/* global jQuery, Zepto, katex, CodeMirror, marked, toolbarIconHandlers, Prism, mermaid */

(function (editormd) {

  window.editormd = editormd();
})(function () {
  /* Require.js assignment replace */

  var $ = typeof jQuery !== "undefined" ? jQuery : Zepto;

  if (typeof $ === "undefined") {
    return;
  }
  /**
   * editormd
   *
   * @param   {String} id       编辑器的ID
   * @param   {Object} options    配置选项 Key/Value
   * @returns {Object} editormd   返回editormd对象
   */

  var editormd = function (id, options) {
    return new editormd.fn.init(id, options);
  };

  editormd.title = editormd.$name = "Editor.md";
  editormd.version = "2.0.2";
  editormd.homePage = "https://github.com/JaxsonWang/editormd/";
  editormd.classPrefix = "editormd-";

  editormd.toolbarModes = {
    full: [
      "undo",
      "redo",
      "|",
      "bold",
      "del",
      "italic",
      "quote",
      "ucwords",
      "uppercase",
      "lowercase",
      "|",
      "h1",
      "h2",
      "h3",
      "h4",
      "h5",
      "h6",
      "|",
      "list-ul",
      "list-ol",
      "hr",
      "|",
      "link",
      "reference-link",
      "image",
      "code",
      "preformatted-text",
      "code-block",
      "table",
      "datetime",
      "emoji",
      "html-entities",
      "pagebreak",
      "more",
      "|",
      "goto-line",
      "watch",
      "preview",
      "fullscreen",
      "clear",
      "search",
      "|",
      "help",
      "info"
    ],
    simple: [
      "undo",
      "redo",
      "|",
      "bold",
      "del",
      "italic",
      "quote",
      "uppercase",
      "lowercase",
      "|",
      "h1",
      "h2",
      "h3",
      "h4",
      "h5",
      "h6",
      "|",
      "list-ul",
      "list-ol",
      "hr",
      "|",
      "watch",
      "preview",
      "fullscreen",
      "|",
      "help",
      "info"
    ],
    mini: ["undo", "redo", "|", "watch", "preview", "|", "help", "info"]
  };

  editormd.defaults = {
    mode: "gfm", //gfm or markdown
    name: "", // Form element name
    value: "", // value for CodeMirror, if mode not gfm/markdown
    theme: "", // Editor.md self themes, before v1.5.0 is CodeMirror theme, default empty
    editorTheme: "default", // Editor area, this is CodeMirror theme at v1.5.0
    previewTheme: "", // Preview area theme, default empty
    markdown: "", // Markdown source code
    appendMarkdown: "", // if in init textarea value not empty, append markdown to textarea
    width: "100%",
    height: "100%",
    path: "./lib/", // Dependents module file directory
    pluginPath: "", // If this empty, default use settings.path + "../plugins/"
    delay: 300, // Delay parse markdown to html, Uint : ms
    autoLoadModules: true, // Automatic load dependent module files
    watch: true,
    placeholder: "Enjoy Markdown! coding now...",
    gotoLine: true,
    codeFold: false,
    autoHeight: false,
    minHeight: "150px",
    maxHeight: "none", // default "none"
    autoFocus: true,
    autoCloseTags: true,
    searchReplace: true,
    syncScrolling: true, // true | false | "single", default true
    readOnly: false,
    tabSize: 4,
    indentUnit: 4,
    lineNumbers: true,
    lineWrapping: true,
    autoCloseBrackets: true,
    showTrailingSpace: true,
    matchBrackets: true,
    indentWithTabs: true,
    styleSelectedText: true,
    matchWordHighlight: true, // options: true, false, "onselected"
    styleActiveLine: true, // Highlight the current line
    dialogLockScreen: true,
    dialogShowMask: true,
    dialogDraggable: true,
    dialogMaskBgColor: "#fff",
    dialogMaskOpacity: 0.1,
    fontSize: "13px",
    saveHTMLToTextarea: false,
    disabledKeyMaps: [],
    onload: function () {
    },
    onresize: function () {
    },
    onchange: function () {
    },
    onwatch: null,
    onunwatch: null,
    onpreviewing: function () {
    },
    onpreviewed: function () {
    },
    onfullscreen: function () {
    },
    onfullscreenExit: function () {
    },
    onscroll: function () {
    },
    onpreviewscroll: function () {
    },
    imageUpload: false,
    imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
    imageUploadURL: "",
    crossDomainUpload: false,
    uploadCallbackURL: "",
    toc: true, // Table of contents
    tocm: false, // Using [TOCM], auto create ToC dropdown menu
    tocTitle: "", // for ToC dropdown menu btn
    tocDropdown: false,
    tocContainer: "",
    tocStartLevel: 1, // Said from H1 to create ToC
    htmlDecode: false, // Open the HTML tag identification
    pageBreak: true, // Enable parse page break <!--nextpage-->
    more: true, // Enable parse page break <!--more-->
    atLink: true, // for @link
    emailLink: true, // for email address auto link
    taskList: false, // Enable Github Flavored Markdown task lists
    emoji: false, // :emoji: , Support Github emoji, Twitter Emoji (Twemoji);
    // Support FontAwesome icon emoji :fa-xxx: > Using fontAwesome icon web fonts;
    // Support Editor.md logo icon emoji :editormd-logo: :editormd-logo-1x: > 1~8x;
    tex: false, // TeX(LaTeX), based on KaTeX
    previewCodeHighlight: true,
    prismTheme: "default", // Prism Theme Style
    prismLineNumbers: false,
    mind: false, //MindMap
    mermaid: false, //mermaid
    toolbar: true, // show/hide toolbar
    toolbarAutoFixed: true, // on window scroll auto fixed position
    toolbarIcons: "full",
    toolbarTitles: {},
    toolbarHandlers: {
      ucwords: function () {
        return editormd.toolbarHandlers.ucwords;
      },
      lowercase: function () {
        return editormd.toolbarHandlers.lowercase;
      }
    },
    toolbarCustomIcons: {
      // using html tag create toolbar icon, unused default <a> tag.
      lowercase:
        "<a href=\"javascript:;\" title=\"Lowercase\" unselectable=\"on\"><i class=\"fa\" name=\"lowercase\" style=\"font-size:24px;margin-top: -10px;\">a</i></a>",
      ucwords:
        "<a href=\"javascript:;\" title=\"ucwords\" unselectable=\"on\"><i class=\"fa\" name=\"ucwords\" style=\"font-size:20px;margin-top: -3px;\">Aa</i></a>"
    },
    toolbarIconsClass: {
      undo: "fa-undo",
      redo: "fa-repeat",
      bold: "fa-bold",
      del: "fa-strikethrough",
      italic: "fa-italic",
      quote: "fa-quote-left",
      uppercase: "fa-font",
      h1: editormd.classPrefix + "bold",
      h2: editormd.classPrefix + "bold",
      h3: editormd.classPrefix + "bold",
      h4: editormd.classPrefix + "bold",
      h5: editormd.classPrefix + "bold",
      h6: editormd.classPrefix + "bold",
      "list-ul": "fa-list-ul",
      "list-ol": "fa-list-ol",
      hr: "fa-minus",
      link: "fa-link",
      "reference-link": "fa-anchor",
      image: "fa-picture-o",
      code: "fa-code",
      "preformatted-text": "fa-file-code-o",
      "code-block": "fa-file-code-o",
      table: "fa-table",
      datetime: "fa-clock-o",
      emoji: "fa-smile-o",
      "html-entities": "fa-copyright",
      pagebreak: "fa-newspaper-o",
      more: "fa-ellipsis-h",
      "goto-line": "fa-terminal", // fa-crosshairs
      watch: "fa-eye-slash",
      unwatch: "fa-eye",
      preview: "fa-desktop",
      search: "fa-search",
      fullscreen: "fa-arrows-alt",
      clear: "fa-eraser",
      help: "fa-question-circle",
      info: "fa-info-circle"
    },
    toolbarIconTexts: {},
    lang: {
      name: "zh-cn",
      description:
        "开源在线Markdown编辑器<br/>Open source online Markdown editor.",
      tocTitle: "目录",
      toolbar: {
        undo: "撤销（Ctrl+Z）",
        redo: "重做（Ctrl+Y）",
        bold: "粗体",
        del: "删除线",
        italic: "斜体",
        quote: "引用",
        ucwords: "将每个单词首字母转成大写",
        uppercase: "将所选转换成大写",
        lowercase: "将所选转换成小写",
        h1: "标题1",
        h2: "标题2",
        h3: "标题3",
        h4: "标题4",
        h5: "标题5",
        h6: "标题6",
        "list-ul": "无序列表",
        "list-ol": "有序列表",
        hr: "横线",
        link: "链接",
        "reference-link": "引用链接",
        image: "添加图片",
        code: "行内代码",
        "preformatted-text": "预格式文本 / 代码块（缩进风格）",
        "code-block": "代码块（多语言风格）",
        table: "添加表格",
        datetime: "日期时间",
        emoji: "Emoji表情",
        "html-entities": "HTML实体字符",
        pagebreak: "插入分页符",
        more: "插入摘要符",
        "goto-line": "跳转到行",
        watch: "关闭实时预览",
        unwatch: "开启实时预览",
        preview: "全窗口预览HTML（按 Shift + ESC还原）",
        fullscreen: "全屏（按ESC还原）",
        clear: "清空",
        search: "搜索",
        help: "使用帮助",
        info: "关于" + editormd.title
      },
      buttons: {
        enter: "确定",
        cancel: "取消",
        close: "关闭"
      },
      dialog: {
        link: {
          title: "添加链接",
          url: "链接地址",
          urlTitle: "链接标题",
          urlEmpty: "错误：请填写链接地址。"
        },
        referenceLink: {
          title: "添加引用链接",
          name: "引用名称",
          url: "链接地址",
          urlId: "链接ID",
          urlTitle: "链接标题",
          nameEmpty: "错误：引用链接的名称不能为空。",
          idEmpty: "错误：请填写引用链接的ID。",
          urlEmpty: "错误：请填写引用链接的URL地址。"
        },
        image: {
          title: "添加图片",
          url: "图片地址",
          link: "图片链接",
          alt: "图片描述",
          uploadButton: "本地上传",
          imageURLEmpty: "错误：图片地址不能为空。",
          uploadFileEmpty: "错误：上传的图片不能为空。",
          formatNotAllowed:
            "错误：只允许上传图片文件，允许上传的图片文件格式有："
        },
        preformattedText: {
          title: "添加预格式文本或代码块",
          emptyAlert: "错误：请填写预格式文本或代码的内容。"
        },
        codeBlock: {
          title: "添加代码块",
          selectLabel: "代码语言：",
          selectDefaultText: "请选择代码语言",
          otherLanguage: "其他语言",
          unselectedLanguageAlert: "错误：请选择代码所属的语言类型。",
          codeEmptyAlert: "错误：请填写代码内容。"
        },
        htmlEntities: {
          title: "HTML 实体字符"
        },
        help: {
          title: "使用帮助"
        }
      }
    }
  };
  editormd.classNames = {
    inline_tex: editormd.classPrefix + "inline-tex",
    block_tex: editormd.classPrefix + "block-tex"
  };
  editormd.dialogZindex = 99999;
  editormd.$katex = null;
  editormd.$marked = null;
  editormd.$mermaid = null;
  editormd.$CodeMirror = null;
  editormd.$prettyPrint = null;

  var timer, mindTimer, mermaidTimer;

  editormd.prototype = editormd.fn = {
    state: {
      watching: false,
      loaded: false,
      preview: false,
      fullscreen: false
    },

    /**
     * 构造函数/实例初始化
     * Constructor / instance initialization
     *
     * @param   {String}   id      编辑器的ID
     * @param   {Object}   [options={}]  配置选项 Key/Value
     * @returns {editormd}         返回editormd的实例对象
     */

    init: function (id, options) {
      options = options || {};
      if (typeof id === "object") {
        options = id;
      }
      var classPrefix = (this.classPrefix = editormd.classPrefix);
      var settings = (this.settings = $.extend(
        true,
        editormd.defaults,
        options
      ));
      id = typeof id === "object" ? settings.id : id;

      var editor = (this.editor = $("#" + id));
      this.id = id;
      this.lang = settings.lang;
      var classNames = (this.classNames = {
        textarea: {
          html: classPrefix + "html-textarea",
          markdown: classPrefix + "markdown-textarea"
        }
      });
      settings.pluginPath =
        settings.pluginPath === ""
          ? settings.path + "../plugins/"
          : settings.pluginPath;

      this.state.watching = settings.watch ? true : false;
      if (!editor.hasClass("editormd")) {
        editor.addClass("editormd");
      }
      editor.css({
        width:
          typeof settings.width === "number"
            ? settings.width + "px"
            : settings.width,
        height:
          typeof settings.height === "number"
            ? settings.height + "px"
            : settings.height
      });
      if (settings.autoHeight) {
        editor.css({
          height: "auto",
          minHeight: settings.minHeight
        });
      }
      var markdownTextarea = (this.markdownTextarea = editor.children(
        "textarea"
      ));
      if (markdownTextarea.length < 1) {
        editor.append("<textarea></textarea>");
        markdownTextarea = this.markdownTextarea = editor.children("textarea");
      }
      markdownTextarea
        .addClass(classNames.textarea.markdown)
        .attr("placeholder", settings.placeholder);
      if (
        typeof markdownTextarea.attr("name") === "undefined" ||
        markdownTextarea.attr("name") === ""
      ) {
        markdownTextarea.attr(
          "name",
          settings.name !== "" ? settings.name : id + "-markdown-doc"
        );
      }
      var appendElements = [
        !settings.readOnly
          ? "<a href=\"javascript:;\" class=\" " +
          classPrefix +
          "preview-close-btn\"><i class=\"fa fa-close \"></i></a>"
          : "",
        settings.saveHTMLToTextarea
          ? "<textarea class=\"" +
          classNames.textarea.html +
          "\" name=\"" +
          id +
          "-html-code\"></textarea>"
          : "",
        "<div class=\"" +
        classPrefix +
        "preview\"><div class=\"markdown-body " +
        classPrefix +
        "preview-container\"></div></div>",
        "<div class=\"" +
        classPrefix +
        "container-mask\" style=\"display:block;\"></div>",
        "<div class=\"" + classPrefix + "mask\"></div>"
      ].join("\n");
      editor.append(appendElements).addClass(classPrefix + "vertical");
      if (settings.theme !== "") {
        editor.addClass(classPrefix + "theme-" + settings.theme);
      }
      this.mask = editor.children("." + classPrefix + "mask");
      this.containerMask = editor.children("." + classPrefix + "container-mask");
      if (settings.markdown !== "") {
        markdownTextarea.val(settings.markdown);
      }
      if (settings.appendMarkdown !== "") {
        markdownTextarea.val(markdownTextarea.val() + settings.appendMarkdown);
      }
      this.htmlTextarea = editor.children("." + classNames.textarea.html);
      this.preview = editor.children("." + classPrefix + "preview");
      this.previewContainer = this.preview.children(
        "." + classPrefix + "preview-container"
      );
      if (settings.previewTheme !== "") {
        this.preview.addClass(
          classPrefix + "preview-theme-" + settings.previewTheme
        );
      }
      if (settings.prismTheme !== "") {
        if (settings.prismTheme === "default") {
          editormd.loadCSS(editormd.prismURL.url + "/themes/prism");
        } else if (editormd.regexs.url.test(settings.prismTheme)) {
          editormd.loadCSS(settings.prismTheme, undefined, undefined, false);
        } else {
          editormd.loadCSS(editormd.prismURL.url + "/themes/prism-" + settings.prismTheme);
        }
      }

      if (settings.prismLineNumbers) {
        editormd.loadCSS(editormd.prismURL.url + "/plugins/line-numbers/prism-line-numbers");
        editormd.loadScript(editormd.prismURL.url + "/plugins/line-numbers/prism-line-numbers");
      }

      if (!settings.autoLoadModules) {
        if (typeof CodeMirror !== "undefined") {
          editormd.$CodeMirror = CodeMirror;
        }

        if (typeof marked !== "undefined") {
          editormd.$marked = marked;
        }
        this.setCodeMirror()
          .setToolbar()
          .loadedDisplay();
      } else {
        this.loadQueues();
      }
      return this;
    },

    /**
     * 所需组件加载队列
     * Required components loading queue
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    loadQueues: function () {
      var _this = this;
      var settings = this.settings;
      var loadPath = settings.path;
      var loadMermaid = function () {
        if (editormd.isIE8) {
          _this.loadedDisplay();
          return;
        }
        if (settings.mind) {
          editormd.loadScript(editormd.mindMapURL, function () {
            _this.loadedDisplay();
          });
        } else {
          _this.loadedDisplay();
        }
      };

      editormd.loadCSS(editormd.codeMirrorURL.url + "/lib/codemirror");
      if (settings.searchReplace && !settings.readOnly) {
        editormd.loadCSS(editormd.codeMirrorURL.url + "/addon/dialog/dialog");
        editormd.loadCSS(editormd.codeMirrorURL.url + "/addon/search/matchesonscrollbar");
      }
      if (settings.codeFold) {
        editormd.loadCSS(editormd.codeMirrorURL.url + "/addon/fold/foldgutter");
      }
      editormd.loadScript(editormd.codeMirrorURL.url + "/lib/codemirror", function () {
        editormd.$CodeMirror = CodeMirror;
        editormd.loadScript(loadPath + "modes.min", function () {
          editormd.loadScript(loadPath + "addons.min", function () {
            _this.setCodeMirror();
            if (settings.mode !== "gfm" && settings.mode !== "markdown") {
              _this.loadedDisplay();
              return false;
            }
            _this.setToolbar();
            editormd.loadScript(editormd.markedURL.js, function () {
              editormd.$marked = marked;

              if (settings.previewCodeHighlight) {
                editormd.loadScript(loadPath + "prism.min", function () {
                  loadMermaid();
                });
              } else {
                loadMermaid();
              }
            });
          });
        });
      });
      return this;
    },

    /**
     * 设置 Editor.md 的整体主题，主要是工具栏
     * Setting Editor.md theme
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setTheme: function (theme) {
      var editor = this.editor;
      var oldTheme = this.settings.theme;
      var themePrefix = this.classPrefix + "theme-";
      editor.removeClass(themePrefix + oldTheme).addClass(themePrefix + theme);
      this.settings.theme = theme;
      return this;
    },

    /**
     * 设置 CodeMirror（编辑区）的主题
     * Setting CodeMirror (Editor area) theme
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setEditorTheme: function (theme) {
      var settings = this.settings;
      settings.editorTheme = theme;
      if (theme !== "default") {
        editormd.loadCSS(
          editormd.codeMirrorURL.url + "/theme/" + settings.editorTheme
        );
      }
      this.cm.setOption("theme", theme);
      return this;
    },

    /**
     * setEditorTheme() 的别名
     * setEditorTheme() alias
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setCodeMirrorTheme: function (theme) {
      this.setEditorTheme(theme);
      return this;
    },

    /**
     * 设置 Editor.md 的主题
     * Setting Editor.md theme
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setPreviewTheme: function (theme) {
      var preview = this.preview;
      var oldTheme = this.settings.previewTheme;
      var themePrefix = this.classPrefix + "preview-theme-";
      preview.removeClass(themePrefix + oldTheme).addClass(themePrefix + theme);
      this.settings.previewTheme = theme;
      return this;
    },

    /**
     * 配置和初始化CodeMirror组件
     * CodeMirror initialization
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setCodeMirror: function () {
      var settings = this.settings;
      var editor = this.editor;
      if (settings.editorTheme !== "default") {
        editormd.loadCSS(
          editormd.codeMirrorURL.url + "/theme/" + settings.editorTheme
        );
      }
      var codeMirrorConfig = {
        mode: settings.mode,
        theme: settings.editorTheme,
        tabSize: settings.tabSize,
        dragDrop: false,
        autofocus: settings.autoFocus,
        autoCloseTags: settings.autoCloseTags,
        readOnly: settings.readOnly ? "nocursor" : false,
        indentUnit: settings.indentUnit,
        lineNumbers: settings.lineNumbers,
        lineWrapping: settings.lineWrapping,
        extraKeys: {
          "Ctrl-Q": function (cm) {
            cm.foldCode(cm.getCursor());
          }
        },
        foldGutter: settings.codeFold,
        gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
        matchBrackets: settings.matchBrackets,
        indentWithTabs: settings.indentWithTabs,
        styleActiveLine: settings.styleActiveLine,
        styleSelectedText: settings.styleSelectedText,
        autoCloseBrackets: settings.autoCloseBrackets,
        showTrailingSpace: settings.showTrailingSpace,
        highlightSelectionMatches: !settings.matchWordHighlight
          ? false
          : {
            showToken:
              settings.matchWordHighlight === "onselected" ? false : /\w/
          }
      };

      this.codeEditor = this.cm = editormd.$CodeMirror.fromTextArea(
        this.markdownTextarea[0],
        codeMirrorConfig
      );
      this.codeMirror = this.cmElement = editor.children(".CodeMirror");

      if (settings.value !== "") {
        this.cm.setValue(settings.value);
      }

      this.codeMirror.css({
        fontSize: settings.fontSize,
        width: !settings.watch ? "100%" : "50%"
      });

      if (settings.autoHeight) {
        this.codeMirror.css({
          height: "auto",
          minHeight: settings.minHeight,
          maxHeight: settings.maxHeight
        });
        this.codeMirror.find(".CodeMirror-gutters").css({
          minHeight: settings.minHeight
        });
        this.codeMirror.find(".CodeMirror-scroll").css({
          minHeight: settings.minHeight,
          maxHeight: settings.maxHeight
        });
        this.cm.setOption("viewportMargin", Infinity);
      }

      if (!settings.lineNumbers) {
        this.codeMirror.find(".CodeMirror-gutters").css("border-right", "none");
      }

      return this;
    },

    /**
     * 获取CodeMirror的配置选项
     * Get CodeMirror setting options
     *
     * @returns {Mixed}          return CodeMirror setting option value
     */

    getCodeMirrorOption: function (key) {
      return this.cm.getOption(key);
    },

    /**
     * 配置和重配置CodeMirror的选项
     * CodeMirror setting options / resettings
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setCodeMirrorOption: function (key, value) {
      this.cm.setOption(key, value);

      return this;
    },

    /**
     * 添加 CodeMirror 键盘快捷键
     * Add CodeMirror keyboard shortcuts key map
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    addKeyMap: function (map, bottom) {
      this.cm.addKeyMap(map, bottom);

      return this;
    },

    /**
     * 移除 CodeMirror 键盘快捷键
     * Remove CodeMirror keyboard shortcuts key map
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    removeKeyMap: function (map) {
      this.cm.removeKeyMap(map);

      return this;
    },

    /**
     * 跳转到指定的行
     * Goto CodeMirror line
     *
     * @param   {String|Intiger}   line    line number or "first"|"last"
     * @returns {editormd}           返回editormd的实例对象
     */

    gotoLine: function (line) {
      var settings = this.settings;
      if (!settings.gotoLine) {
        return this;
      }
      var cm = this.cm;
      var count = cm.lineCount();
      var preview = this.preview;
      if (typeof line === "string") {
        if (line === "last") {
          line = count;
        }
        if (line === "first") {
          line = 1;
        }
      }
      if (typeof line !== "number") {
        alert("Error: The line number must be an integer.");
        return this;
      }
      line = parseInt(line) - 1;
      if (line > count) {
        alert("Error: The line number range 1-" + count);
        return this;
      }
      cm.setCursor({line: line, ch: 0});
      var scrollInfo = cm.getScrollInfo();
      var clientHeight = scrollInfo.clientHeight;
      var coords = cm.charCoords({line: line, ch: 0}, "local");
      cm.scrollTo(null, (coords.top + coords.bottom - clientHeight) / 2);
      if (settings.watch) {
        var cmScroll = this.codeMirror.find(".CodeMirror-scroll")[0];
        var height = $(cmScroll).height();
        var scrollTop = cmScroll.scrollTop;
        var percent = scrollTop / cmScroll.scrollHeight;
        if (scrollTop === 0) {
          preview.scrollTop(0);
        } else if (scrollTop + height >= cmScroll.scrollHeight - 16) {
          preview.scrollTop(preview[0].scrollHeight);
        } else {
          preview.scrollTop(preview[0].scrollHeight * percent);
        }
      }
      cm.focus();
      return this;
    },

    /**
     * 扩展当前实例对象，可同时设置多个或者只设置一个
     * Extend editormd instance object, can mutil setting.
     *
     * @returns {editormd}          this(editormd instance object.)
     */

    extend: function () {
      if (typeof arguments[1] !== "undefined") {
        if (typeof arguments[1] === "function") {
          arguments[1] = $.proxy(arguments[1], this);
        }
        this[arguments[0]] = arguments[1];
      }
      if (
        typeof arguments[0] === "object" &&
        typeof arguments[0].length === "undefined"
      ) {
        $.extend(true, this, arguments[0]);
      }
      return this;
    },

    /**
     * 设置或扩展当前实例对象，单个设置
     * Extend editormd instance object, one by one
     *
     * @param   {String|Object}   key     option key
     * @param   {String|Object}   value   option value
     * @returns {editormd}          this(editormd instance object.)
     */

    set: function (key, value) {
      if (typeof value !== "undefined" && typeof value === "function") {
        value = $.proxy(value, this);
      }
      this[key] = value;
      return this;
    },

    /**
     * 重新配置
     * Resetting editor options
     *
     * @param   {String|Object}   key     option key
     * @param   {String|Object}   value   option value
     * @returns {editormd}          this(editormd instance object.)
     */

    config: function (key, value) {
      var settings = this.settings;
      if (typeof key === "object") {
        settings = $.extend(true, settings, key);
      }
      if (typeof key === "string") {
        settings[key] = value;
      }
      this.settings = settings;
      this.recreate();
      return this;
    },

    /**
     * 注册事件处理方法
     * Bind editor event handle
     *
     * @param   {String}   eventType    event type
     * @param   {Function}   callback     回调函数
     * @returns {editormd}          this(editormd instance object.)
     */

    on: function (eventType, callback) {
      var settings = this.settings;
      if (typeof settings["on" + eventType] !== "undefined") {
        settings["on" + eventType] = $.proxy(callback, this);
      }
      return this;
    },

    /**
     * 解除事件处理方法
     * Unbind editor event handle
     *
     * @param   {String}   eventType      event type
     * @returns {editormd}          this(editormd instance object.)
     */

    off: function (eventType) {
      var settings = this.settings;
      if (typeof settings["on" + eventType] !== "undefined") {
        settings["on" + eventType] = function () {
        };
      }
      return this;
    },

    /**
     * 显示工具栏
     * Display toolbar
     *
     * @param   {Function} [callback=function(){}] 回调函数
     * @returns {editormd}  返回editormd的实例对象
     */

    showToolbar: function (callback) {
      var settings = this.settings;
      if (settings.readOnly) {
        return this;
      }
      if (
        settings.toolbar &&
        (this.toolbar.length < 1 ||
          this.toolbar.find("." + this.classPrefix + "menu").html() === "")
      ) {
        this.setToolbar();
      }
      settings.toolbar = true;
      this.toolbar.show();
      this.resize();
      $.proxy(callback || function () {
      }, this)();
      return this;
    },

    /**
     * 隐藏工具栏
     * Hide toolbar
     *
     * @param   {Function} [callback=function(){}] 回调函数
     * @returns {editormd}             this(editormd instance object.)
     */

    hideToolbar: function (callback) {
      var settings = this.settings;
      settings.toolbar = false;
      this.toolbar.hide();
      this.resize();
      $.proxy(callback || function () {
      }, this)();
      return this;
    },

    /**
     * 页面滚动时工具栏的固定定位
     * Set toolbar in window scroll auto fixed position
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setToolbarAutoFixed: function (fixed) {
      var state = this.state;
      var editor = this.editor;
      var toolbar = this.toolbar;
      var settings = this.settings;
      if (typeof fixed !== "undefined") {
        settings.toolbarAutoFixed = fixed;
      }
      var autoFixedHandle = function () {
        var $window = $(window);
        var top = $window.scrollTop();
        if (!settings.toolbarAutoFixed) {
          return false;
        }
        if (top - editor.offset().top > 10 && top - editor.offset().top < editor.height() - toolbar.height()) {
          toolbar.css({
            position: "fixed",
            width: editor.width() + "px",
            left: editor.offset().left + "px"
          });
        } else {
          toolbar.css({
            position: "absolute",
            width: "100%",
            left: 0
          });
        }
      };
      if (
        !state.fullscreen &&
        !state.preview &&
        settings.toolbar &&
        settings.toolbarAutoFixed
      ) {
        $(window).bind("scroll", autoFixedHandle);
      }
      return this;
    },

    /**
     * 配置和初始化工具栏
     * Set toolbar and Initialization
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setToolbar: function () {
      var settings = this.settings;
      if (settings.readOnly) {
        return this;
      }
      var editor = this.editor;
      var classPrefix = this.classPrefix;
      var toolbar = (this.toolbar = editor.children(
        "." + classPrefix + "toolbar"
      ));
      if (settings.toolbar && toolbar.length < 1) {
        var toolbarHTML =
          "<div class=\"" +
          classPrefix +
          "toolbar\"><div class=\"" +
          classPrefix +
          "toolbar-container\"><ul class=\"" +
          classPrefix +
          "menu\"></ul></div></div>";
        editor.append(toolbarHTML);
        toolbar = this.toolbar = editor.children("." + classPrefix + "toolbar");
      }
      if (!settings.toolbar) {
        toolbar.hide();
        return this;
      }
      toolbar.show();
      var icons =
        typeof settings.toolbarIcons === "function"
          ? settings.toolbarIcons()
          : typeof settings.toolbarIcons === "string"
            ? editormd.toolbarModes[settings.toolbarIcons]
            : settings.toolbarIcons;
      var toolbarMenu = toolbar.find("." + this.classPrefix + "menu"),
        menu = "";
      var pullRight = false;
      for (var i = 0, len = icons.length; i < len; i++) {
        var name = icons[i];
        if (name === "||") {
          pullRight = true;
        } else if (name === "|") {
          menu += "<li class=\"divider\" unselectable=\"on\">|</li>";
        } else {
          var isHeader = /h(\d)/.test(name);
          var index = name;
          if (name === "watch" && !settings.watch) {
            index = "unwatch";
          }
          var title = settings.lang.toolbar[index];
          var iconTexts = settings.toolbarIconTexts[index];
          var iconClass = settings.toolbarIconsClass[index];
          title = typeof title === "undefined" ? "" : title;
          iconTexts = typeof iconTexts === "undefined" ? "" : iconTexts;
          iconClass = typeof iconClass === "undefined" ? "" : iconClass;
          var menuItem = pullRight ? "<li class=\"pull-right\">" : "<li>";
          if (
            typeof settings.toolbarCustomIcons[name] !== "undefined" &&
            typeof settings.toolbarCustomIcons[name] !== "function"
          ) {
            menuItem += settings.toolbarCustomIcons[name];
          } else {
            menuItem +=
              "<a href=\"javascript:;\" class=\"tooltip\" unselectable=\"on\">";
            menuItem +=
              "<i class=\"fa " +
              iconClass +
              "\" name=\"" +
              name +
              "\" unselectable=\"on\">" +
              (isHeader
                ? name.toUpperCase()
                : iconClass === "" ? iconTexts : "") +
              "</i>";
            menuItem +=
              "<span class=\"tooltiptext\">" + title + "</span>";
            menuItem += "</a>";
          }
          menuItem += "</li>";
          menu = pullRight ? menuItem + menu : menu + menuItem;
        }
      }
      toolbarMenu.html(menu);
      toolbarMenu
        .find("[title=\"Lowercase\"]")
        .attr("title", settings.lang.toolbar.lowercase);
      toolbarMenu
        .find("[title=\"ucwords\"]")
        .attr("title", settings.lang.toolbar.ucwords);
      this.setToolbarHandler();
      this.setToolbarAutoFixed();
      return this;
    },

    /**
     * 工具栏图标事件处理对象序列
     * Get toolbar icons event handlers
     *
     * @param   {Object}   cm  CodeMirror的实例对象
     * @param   {String}   name  要获取的事件处理器名称
     * @returns {Object}     返回处理对象序列
     */

    dialogLockScreen: function () {
      $.proxy(editormd.dialogLockScreen, this)();
      return this;
    },
    dialogShowMask: function (dialog) {
      $.proxy(editormd.dialogShowMask, this)(dialog);
      return this;
    },
    getToolbarHandles: function (name) {
      var toolbarHandlers = (this.toolbarHandlers = editormd.toolbarHandlers);
      return name && typeof toolbarIconHandlers[name] !== "undefined"
        ? toolbarHandlers[name]
        : toolbarHandlers;
    },

    /**
     * 工具栏图标事件处理器
     * Bind toolbar icons event handle
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    setToolbarHandler: function () {
      var _this = this;
      var settings = this.settings;
      if (!settings.toolbar || settings.readOnly) {
        return this;
      }
      var toolbar = this.toolbar;
      var cm = this.cm;
      var classPrefix = this.classPrefix;
      var toolbarIcons = (this.toolbarIcons = toolbar.find(
        "." + classPrefix + "menu > li > a"
      ));
      var toolbarIconHandlers = this.getToolbarHandles();
      toolbarIcons.bind(editormd.mouseOrTouch("click", "touchend"), function () {
        var icon = $(this).children(".fa");
        var name = icon.attr("name");
        var cursor = cm.getCursor();
        var selection = cm.getSelection();
        if (name === "") {
          return;
        }
        _this.activeIcon = icon;
        if (typeof toolbarIconHandlers[name] !== "undefined") {
          $.proxy(toolbarIconHandlers[name], _this)(cm);
        } else {
          if (typeof settings.toolbarHandlers[name] !== "undefined") {
            $.proxy(settings.toolbarHandlers[name], _this)(
              cm,
              icon,
              cursor,
              selection
            );
          }
        }
        if (
          name !== "link" &&
          name !== "reference-link" &&
          name !== "image" &&
          name !== "code-block" &&
          name !== "preformatted-text" &&
          name !== "watch" &&
          name !== "preview" &&
          name !== "search" &&
          name !== "fullscreen" &&
          name !== "info"
        ) {
          cm.focus();
        }
        return false;
      });

      return this;
    },

    /**
     * 动态创建对话框
     * Creating custom dialogs
     *
     * @param   {Object} options  配置项键值对 Key/Value
     * @returns {dialog}      返回创建的dialog的jQuery实例对象
     */

    createDialog: function (options) {
      return $.proxy(editormd.createDialog, this)(options);
    },

    /**
     * 创建关于Editor.md的对话框
     * Create about Editor.md dialog
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    createInfoDialog: function () {
      var _this = this;
      var editor = this.editor;
      var classPrefix = this.classPrefix;
      var infoDialogHTML = [
        "<div class='" +
        classPrefix +
        "dialog " +
        classPrefix +
        "dialog-info'>",
        "<div class='" + classPrefix + "dialog-container'>",
        "<h1>" +
        editormd.title +
        "<small>v" +
        editormd.version +
        "</small></h1>",
        "<p>" + this.lang.description + "</p>",
        "<p style='margin: 10px 0 20px 0;'><a href='" +
        editormd.homePage +
        "' target='_blank'>" +
        editormd.homePage +
        " <i class='fa fa-external-link'></i></a></p>",
        "<p style='font-size: 0.85em;'>Base The Old <a href='https://pandao.github.io/editor.md/' rel='nofollow' target='_blank' class='hover-link'>Editor.md</a></p>" +
        "<p style='font-size: 0.85em;'>Copyright &copy; 2018 <a href='https://github.com/JaxsonWang' target='_blank' class='hover-link'>JaxsonWang</a>, The <a href='https://github.com/JaxsonWang/Editormd/blob/master/LICENSE' target='_blank' class='hover-link'>GNU V3</a> License.</p>",
        "</div>",
        "<a href='javascript:;' class='fa fa-close " +
        classPrefix +
        "dialog-close'></a>",
        "</div>"
      ].join("\n");
      editor.append(infoDialogHTML);
      var infoDialog = (this.infoDialog = editor.children(
        "." + classPrefix + "dialog-info"
      ));
      infoDialog
        .find("." + classPrefix + "dialog-close")
        .bind(editormd.mouseOrTouch("click", "touchend"), function () {
          _this.hideInfoDialog();
        });
      infoDialog
        .css("border", editormd.isIE8 ? "1px solid #ddd" : "")
        .css("z-index", editormd.dialogZindex)
        .show();
      this.infoDialogPosition();
      return this;
    },

    /**
     * 关于Editor.md对话居中定位
     * Editor.md dialog position handle
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    infoDialogPosition: function () {
      var infoDialog = this.infoDialog;
      var _infoDialogPosition = function () {
        infoDialog.css({
          top: ($(window).height() - infoDialog.height()) / 2 + "px",
          left: ($(window).width() - infoDialog.width()) / 2 + "px"
        });
      };
      _infoDialogPosition();
      $(window).resize(_infoDialogPosition);
      return this;
    },

    /**
     * 显示关于Editor.md
     * Display about Editor.md dialog
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    showInfoDialog: function () {
      $("html,body").css("overflow-x", "hidden");
      var editor = this.editor;
      var settings = this.settings;
      var infoDialog = (this.infoDialog = editor.children(
        "." + this.classPrefix + "dialog-info"
      ));
      if (infoDialog.length < 1) {
        this.createInfoDialog();
      }
      this.lockScreen(true);
      this.mask
        .css({
          opacity: settings.dialogMaskOpacity,
          backgroundColor: settings.dialogMaskBgColor
        })
        .show();
      infoDialog.css("z-index", editormd.dialogZindex).show();
      this.infoDialogPosition();
      return this;
    },

    /**
     * 隐藏关于Editor.md
     * Hide about Editor.md dialog
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    hideInfoDialog: function () {
      $("html,body").css("overflow-x", "");
      this.infoDialog.hide();
      this.mask.hide();
      this.lockScreen(false);
      return this;
    },

    /**
     * 锁屏
     * lock screen
     *
     * @param   {Boolean}  lock  Boolean 布尔值，是否锁屏
     * @returns {editormd}       返回editormd的实例对象
     */

    lockScreen: function (lock) {
      editormd.lockScreen(lock);
      this.resize();
      return this;
    },

    /**
     * 编辑器界面重建，用于动态语言包或模块加载等
     * Recreate editor
     *
     * @returns {editormd}  返回editormd的实例对象
     */

    recreate: function () {
      var editor = this.editor;
      var settings = this.settings;
      this.codeMirror.remove();
      this.setCodeMirror();
      if (!settings.readOnly) {
        if (editor.find(".editormd-dialog").length > 0) {
          editor.find(".editormd-dialog").remove();
        }
        if (settings.toolbar) {
          this.getToolbarHandles();
          this.setToolbar();
        }
      }
      this.loadedDisplay(true);
      return this;
    },

    /**
     * 高亮预览HTML的pre代码部分
     * highlight of preview codes
     *
     * @returns {editormd}       返回editormd的实例对象
     */

    previewCodeHighlight: function () {
      var settings = this.settings;
      var previewContainer = this.previewContainer;
      if (settings.previewCodeHighlight) {
        previewContainer.find("pre").addClass("prism-highlight");
      }
      if (settings.prismLineNumbers) {
        previewContainer.find("pre").addClass("line-numbers");
      }

      Prism.highlightAll();

      return this;
    },

    /**
     * 解析TeX(KaTeX)科学公式
     * TeX(KaTeX) Renderer
     *
     * @returns {editormd}       返回editormd的实例对象
     */

    katexRender: function () {
      if (timer === null) {
        return this;
      }
      //行内公式
      this.previewContainer.find("." + editormd.classNames.inline_tex).each(function () {
        var tex = $(this);
        editormd.$katex.render(tex.text(), tex[0]);
        tex.find(".katex").css("font-size", "1.6em");
      });
      //多行公式
      this.previewContainer.find("." + editormd.classNames.block_tex).each(function () {
        var tex = $(this);
        editormd.$katex.render(tex.text(), tex[0], {displayMode: true});
        tex.find(".katex").css("font-size", "1.6em");
      });
      return this;
    },

    /**
     * 解析和渲染思维导图
     * mindmap Renderer
     *
     * @returns {editormd}       返回editormd的实例对象
     */
    mindRender: function () {
      if (mindTimer === null) {
        return this;
      }
      this.previewContainer.find(".mind").each(function () {
        var mind = $(this);
        mind.drawMind();
        //console.log(mind)
      });
      $(".mind").drawMind();

      return this;
    },

    /**
     * 解析和渲染Mermaid
     * mermaid Render
     *
     * @returns {editormd}       返回editormd的实例对象
     */
    mermaidRender: function () {
      if (mermaidTimer === null) {
        return this;
      }
      this.previewContainer.find(".mermaid").each(function () {
        editormd.$mermaid.init({
          startOnLoad: true
        }, ".mermaid");
      });

      var $this = this;
      var settings = this.settings;
      var preview = $this.preview;
      var codeMirror = $this.codeMirror;
      var codeView = codeMirror.find(".CodeMirror-scroll");
      var height = codeView.height();
      var scrollTop = codeView.scrollTop();
      var percent = scrollTop / codeView[0].scrollHeight;
      var tocHeight = 0;
      preview.find(".markdown-toc-list").each(function () {
        tocHeight += $(this).height();
      });
      var tocMenuHeight = preview.find(".editormd-toc-menu").height();
      tocMenuHeight = !tocMenuHeight ? 0 : tocMenuHeight;
      if (settings.syncScrolling) {
        if (scrollTop === 0) {
          preview.scrollTop(0);
        } else if (scrollTop + height >= codeView[0].scrollHeight - 16) {
          preview.scrollTop(preview[0].scrollHeight);
        } else {
          preview.scrollTop(
            (preview[0].scrollHeight + tocHeight + tocMenuHeight) * percent
          );
        }
      }

      return this;
    },

    /**
     * 注册键盘快捷键处理
     * Register CodeMirror keyMaps (keyboard shortcuts).
     *
     * @param   {Object}  keyMap    KeyMap key/value {"(Ctrl/Shift/Alt)-Key" : function(){}}
     * @returns {editormd}        return this
     */
    registerKeyMaps: function (keyMap) {
      var _this = this;
      var cm = this.cm;
      var settings = this.settings;
      var toolbarHandlers = editormd.toolbarHandlers;
      var disabledKeyMaps = settings.disabledKeyMaps;
      keyMap = keyMap || null;
      if (keyMap) {
        for (var i in keyMap) {
          if ($.inArray(i, disabledKeyMaps) < 0) {
            var map = {};
            map[i] = keyMap[i];
            cm.addKeyMap(keyMap);
          }
        }
      } else {
        for (var k in editormd.keyMaps) {
          var _keyMap = editormd.keyMaps[k];
          var handle =
            typeof _keyMap === "string"
              ? $.proxy(toolbarHandlers[_keyMap], _this)
              : $.proxy(_keyMap, _this);
          if (
            $.inArray(k, ["F9", "F10", "F11"]) < 0 &&
            $.inArray(k, disabledKeyMaps) < 0
          ) {
            var _map = {};
            _map[k] = handle;
            cm.addKeyMap(_map);
          }
        }
        $(window).keydown(function (event) {
          var keymaps = {
            "120": "F9",
            "121": "F10",
            "122": "F11"
          };
          if ($.inArray(keymaps[event.keyCode], disabledKeyMaps) < 0) {
            switch (event.keyCode) {
              case 120:
                $.proxy(toolbarHandlers.watch, _this)();
                return false;
              case 121:
                $.proxy(toolbarHandlers.preview, _this)();
                return false;
              case 122:
                $.proxy(toolbarHandlers.fullscreen, _this)();
                return false;
              default:
                break;
            }
          }
        });
      }
      return this;
    },

    /**
     * 绑定同步滚动
     * @returns {editormd} return this
     */
    bindScrollEvent: function () {
      var _this = this;
      var preview = this.preview;
      var settings = this.settings;
      var codeMirror = this.codeMirror;
      var mouseOrTouch = editormd.mouseOrTouch;
      if (!settings.syncScrolling) {
        return this;
      }
      var cmBindScroll = function () {
        codeMirror
          .find(".CodeMirror-scroll")
          .bind(mouseOrTouch("scroll", "touchmove"), function (event) {
            var height = $(this).height();
            var scrollTop = $(this).scrollTop();
            var percent = scrollTop / $(this)[0].scrollHeight;
            var tocHeight = 0;
            preview.find(".markdown-toc-list").each(function () {
              tocHeight += $(this).height();
            });
            var tocMenuHeight = preview.find(".editormd-toc-menu").height();
            tocMenuHeight = !tocMenuHeight ? 0 : tocMenuHeight;
            if (scrollTop === 0) {
              preview.scrollTop(0);
            } else if (scrollTop + height >= $(this)[0].scrollHeight - 16) {
              preview.scrollTop(preview[0].scrollHeight);
            } else {
              preview.scrollTop(
                (preview[0].scrollHeight + tocHeight + tocMenuHeight) * percent
              );
            }
            $.proxy(settings.onscroll, _this)(event);
          });
      };
      var cmUnbindScroll = function () {
        codeMirror
          .find(".CodeMirror-scroll")
          .unbind(mouseOrTouch("scroll", "touchmove"));
      };
      var previewBindScroll = function () {
        preview.bind(mouseOrTouch("scroll", "touchmove"), function (event) {
          var height = $(this).height();
          var scrollTop = $(this).scrollTop();
          var percent = scrollTop / $(this)[0].scrollHeight;
          var codeView = codeMirror.find(".CodeMirror-scroll");
          if (scrollTop === 0) {
            codeView.scrollTop(0);
          } else if (scrollTop + height >= $(this)[0].scrollHeight) {
            codeView.scrollTop(codeView[0].scrollHeight);
          } else {
            codeView.scrollTop(codeView[0].scrollHeight * percent);
          }
          $.proxy(settings.onpreviewscroll, _this)(event);
        });
      };

      var previewUnbindScroll = function () {
        preview.unbind(mouseOrTouch("scroll", "touchmove"));
      };
      codeMirror.bind({
        mouseover: cmBindScroll,
        mouseout: cmUnbindScroll,
        touchstart: cmBindScroll,
        touchend: cmUnbindScroll
      });
      if (settings.syncScrolling === "single") {
        return this;
      }
      preview.bind({
        mouseover: previewBindScroll,
        mouseout: previewUnbindScroll,
        touchstart: previewBindScroll,
        touchend: previewUnbindScroll
      });
      return this;
    },

    bindChangeEvent: function () {
      var _this = this;
      var cm = this.cm;
      var settings = this.settings;

      cm.on("change", function () {
        if (settings.watch) {
          _this.previewContainer.css(
            "padding",
            settings.autoHeight ? "20px 20px 50px 40px" : "20px"
          );
        }
        timer = setTimeout(function () {
          clearTimeout(timer);
          _this.save();
          timer = null;
        }, settings.delay);
      });
      return this;
    },

    /**
     * 加载队列完成之后的显示处理
     * Display handle of the module queues loaded after.
     *
     * @param   {Boolean}   recreate   是否为重建编辑器
     * @returns {editormd}       返回editormd的实例对象
     */
    loadedDisplay: function (recreate) {
      recreate = recreate || false;
      var _this = this;
      var editor = this.editor;
      var preview = this.preview;
      var settings = this.settings;
      this.containerMask.hide();
      this.save();
      if (settings.watch) {
        preview.show();
      }
      editor.data("oldWidth", editor.width()).data("oldHeight", editor.height()); // 为了兼容Zepto
      this.resize();
      this.registerKeyMaps();
      $(window).resize(function () {
        _this.resize();
      });
      this.bindScrollEvent().bindChangeEvent();
      if (!recreate) {
        $.proxy(settings.onload, this)();
      }
      this.state.loaded = true;
      return this;
    },

    /**
     * 设置编辑器的宽度
     * Set editor width
     *
     * @param   {Number|String} width  编辑器宽度值
     * @returns {editormd}       返回editormd的实例对象
     */
    width: function (width) {
      this.editor.css("width", typeof width === "number" ? width + "px" : width);
      this.resize();
      return this;
    },

    /**
     * 设置编辑器的高度
     * Set editor height
     *
     * @param   {Number|String} height  编辑器高度值
     * @returns {editormd}        返回editormd的实例对象
     */
    height: function (height) {
      this.editor.css(
        "height",
        typeof height === "number" ? height + "px" : height
      );
      this.resize();
      return this;
    },

    /**
     * 调整编辑器的尺寸和布局
     * Resize editor layout
     *
     * @param   {Number|String} [width=null]  编辑器宽度值
     * @param   {Number|String} [height=null] 编辑器高度值
     * @returns {editormd}          返回editormd的实例对象
     */
    resize: function (width, height) {
      width = width || null;
      height = height || null;
      var state = this.state;
      var editor = this.editor;
      var preview = this.preview;
      var toolbar = this.toolbar;
      var settings = this.settings;
      var codeMirror = this.codeMirror;
      if (width) {
        editor.css("width", typeof width === "number" ? width + "px" : width);
      }
      if (settings.autoHeight && !state.fullscreen && !state.preview) {
        editor.css({
          height: "auto",
          minHeight: settings.minHeight
        });
        codeMirror.css({
          height: "auto",
          minHeight: settings.minHeight,
          maxHeight: settings.maxHeight
        });
      } else {
        if (height) {
          editor.css(
            "height",
            typeof height === "number" ? height + "px" : height
          );
        }
        if (state.fullscreen) {
          editor.height($(window).height());
        }
        if (settings.toolbar && !settings.readOnly) {
          codeMirror.css("margin-top", toolbar.height() + 1).height(editor.height() - toolbar.height());
        } else {
          codeMirror.css("margin-top", 0).height(editor.height());
        }
      }
      if (settings.watch) {
        codeMirror.width(editor.width() / 2);
        preview.width(!state.preview ? editor.width() / 2 : editor.width());
        this.previewContainer.css(
          "padding",
          settings.autoHeight ? "20px 20px 50px 40px" : "20px"
        );
        if (settings.toolbar && !settings.readOnly) {
          preview.css("top", toolbar.height() + 1);
        } else {
          preview.css("top", 0);
        }
        if (settings.autoHeight && !state.fullscreen && !state.preview) {
          preview.height("");
        } else {
          var previewHeight =
            settings.toolbar && !settings.readOnly
              ? editor.height() - toolbar.height()
              : editor.height();
          preview.height(previewHeight);
        }
      } else {
        codeMirror.width(editor.width());
        preview.hide();
      }
      if (state.loaded) {
        $.proxy(settings.onresize, this)();
      }
      return this;
    },

    /**
     * 解析和保存Markdown代码
     * Parse & Saving Markdown source code
     *
     * @returns {editormd}   返回editormd的实例对象
     */
    save: function () {
      if (timer === null) {
        return this;
      }
      var _this = this;
      var state = this.state;
      var settings = this.settings;
      var cm = this.cm;
      var cmValue = cm.getValue();
      var previewContainer = this.previewContainer;
      if (settings.mode !== "gfm" && settings.mode !== "markdown") {
        this.markdownTextarea.val(cmValue);
        return this;
      }
      var marked = editormd.$marked;
      var markdownToC = (this.markdownToC = []);
      var rendererOptions = (this.markedRendererOptions = {
        toc: settings.toc,
        tocm: settings.tocm,
        tocStartLevel: settings.tocStartLevel,
        pageBreak: settings.pageBreak,
        more: settings.more,
        taskList: settings.taskList,
        emoji: settings.emoji,
        tex: settings.tex,
        atLink: settings.atLink, // for @link
        emailLink: settings.emailLink, // for mail address auto link
        previewCodeHighlight: settings.previewCodeHighlight
      });
      var markedOptions = (this.markedOptions = {
        renderer: editormd.markedRenderer(markdownToC, rendererOptions),
        gfm: true,
        tables: true,
        breaks: true,
        pedantic: false,
        sanitize: !settings.htmlDecode, // 关闭忽略HTML标签，即开启识别HTML标签，默认为false
        smartLists: true,
        smartypants: true,
        langPrefix: "language-" //修改语言类名
      });
      marked.setOptions(markedOptions);

      var newMarkdownDoc = editormd.$marked(cmValue, markedOptions);
      newMarkdownDoc = editormd.filterHTMLTags(
        newMarkdownDoc,
        settings.htmlDecode
      );

      //正则匹配 - 分页符
      var nextpage = editormd.regexs.pageBreak;
      if (nextpage.test(newMarkdownDoc)) {
        newMarkdownDoc = newMarkdownDoc.replace(nextpage, "<hr style=\"page-break-after:always;\" class=\"page-break editormd-page-break\" />");
      }

      //正则匹配 - 摘要符
      var more = editormd.regexs.more;
      if (more.test(newMarkdownDoc)) {
        newMarkdownDoc = newMarkdownDoc.replace(more, "<p><img class=\"more\" src=\"\"></p>");
      }

      // 正则匹配 - 行内高亮
      var highlightText = editormd.regexs.highlightText;
      if (highlightText.test(newMarkdownDoc)) {
        newMarkdownDoc = newMarkdownDoc.replace(highlightText, function($1, $2) {
          return "<span class=\"markdown-inline-highlight\" style=\"background-color: #fffd38;\">" + $2 + "</span>";
        });
      }

      // FIXED marked.js render table structure with br tag inside, so this make it removed
      function removeBrTagInTable(text) {
        var tableRegex = /<table[^>]*(.|[\n\r])*?<\/table>/gim;
        text = text.replace(tableRegex, function ($1) {
          return $1.replace(/<br>\s*/g, "");
        });
        return text;
      }

      newMarkdownDoc = removeBrTagInTable(newMarkdownDoc);
      this.markdownTextarea.text(cmValue);
      cm.save();
      if (settings.saveHTMLToTextarea) {
        this.htmlTextarea.text(newMarkdownDoc);
      }
      if (settings.watch || (!settings.watch && state.preview)) {
        previewContainer.html(newMarkdownDoc);
        this.previewCodeHighlight();
        if (settings.toc) {
          var tocContainer =
            settings.tocContainer === ""
              ? previewContainer
              : $(settings.tocContainer);
          var tocMenu = tocContainer.find("." + this.classPrefix + "toc-menu");
          tocContainer.attr(
            "previewContainer",
            settings.tocContainer === "" ? "true" : "false"
          );
          if (settings.tocContainer !== "" && tocMenu.length > 0) {
            tocMenu.remove();
          }
          editormd.markdownToCRenderer(
            markdownToC,
            tocContainer,
            settings.tocDropdown,
            settings.tocStartLevel
          );
          if (
            settings.tocDropdown ||
            tocContainer.find("." + this.classPrefix + "toc-menu").length > 0
          ) {
            editormd.tocDropdownMenu(
              tocContainer,
              settings.tocTitle !== "" ? settings.tocTitle : this.lang.tocTitle
            );
          }
          if (settings.tocContainer !== "") {
            previewContainer.find(".markdown-toc").css("border", "none");
          }
        }

        if (settings.tex) {
          if (!editormd.kaTeXLoaded && settings.autoLoadModules) {
            editormd.loadKaTeX(function () {
              editormd.$katex = katex;
              editormd.kaTeXLoaded = true;
              _this.katexRender();
            });
          } else {
            editormd.$katex = katex;
            this.katexRender();
          }
        }
        if (settings.mind) {
          mindTimer = setTimeout(function () {
            clearTimeout(mindTimer);
            _this.mindRender();
            mindTimer = null;
          }, 10);
          //this.mindRender();
        }
        if (settings.mermaid) {
          if (!editormd.mermaidLoaded && settings.autoLoadModules) {
            editormd.loadMermaid(function () {
              editormd.$mermaid = mermaid;
              editormd.mermaidLoaded = true;
              _this.mermaidRender();
            });
          } else {
            editormd.$mermaid = mermaid;
            this.mermaidRender();
          }

        }

        if (state.loaded) {
          $.proxy(settings.onchange, this)();
        }
      }
      return this;
    },

    /**
     * 聚焦光标位置
     * Focusing the cursor position
     *
     * @returns {editormd}     返回editormd的实例对象
     */
    focus: function () {
      this.cm.focus();
      return this;
    },

    /**
     * 设置光标的位置
     * Set cursor position
     *
     * @param   {Object}  cursor 要设置的光标位置键值对象，例：{line:1, ch:0}
     * @returns {editormd}     返回editormd的实例对象
     */
    setCursor: function (cursor) {
      this.cm.setCursor(cursor);
      return this;
    },

    /**
     * 获取当前光标的位置
     * Get the current position of the cursor
     *
     * @returns {Cursor}     返回一个光标Cursor对象
     */
    getCursor: function () {
      return this.cm.getCursor();
    },

    /**
     * 设置光标选中的范围
     * Set cursor selected ranges
     *
     * @param   {Object}  from   开始位置的光标键值对象，例：{line:1, ch:0}
     * @param   {Object}  to   结束位置的光标键值对象，例：{line:1, ch:0}
     * @returns {editormd}     返回editormd的实例对象
     */
    setSelection: function (from, to) {
      this.cm.setSelection(from, to);
      return this;
    },

    /**
     * 获取光标选中的文本
     * Get the texts from cursor selected
     *
     * @returns {String}     返回选中文本的字符串形式
     */
    getSelection: function () {
      return this.cm.getSelection();
    },

    /**
     * 设置光标选中的文本范围
     * Set the cursor selection ranges
     *
     * @param   {Array}  ranges  cursor selection ranges array
     * @returns {Array}      return this
     */
    setSelections: function (ranges) {
      this.cm.setSelections(ranges);
      return this;
    },

    /**
     * 获取光标选中的文本范围
     * Get the cursor selection ranges
     *
     * @returns {Array}     return selection ranges array
     */
    getSelections: function () {
      return this.cm.getSelections();
    },

    /**
     * 替换当前光标选中的文本或在当前光标处插入新字符
     * Replace the text at the current cursor selected or insert a new character at the current cursor position
     *
     * @param   {String}  value  要插入的字符值
     * @returns {editormd}     返回editormd的实例对象
     */
    replaceSelection: function (value) {
      this.cm.replaceSelection(value);
      return this;
    },

    /**
     * 在当前光标处插入新字符
     * Insert a new character at the current cursor position
     *
     * 同replaceSelection()方法
     * With the replaceSelection() method
     *
     * @param   {String}  value  要插入的字符值
     * @returns {editormd}     返回editormd的实例对象
     */
    insertValue: function (value) {
      this.replaceSelection(value);
      return this;
    },

    /**
     * 追加markdown
     * append Markdown to editor
     *
     * @param   {String}  md   要追加的markdown源文档
     * @returns {editormd}     返回editormd的实例对象
     */
    appendMarkdown: function (md) {
      var cm = this.cm;
      cm.setValue(cm.getValue() + md);
      return this;
    },

    /**
     * 设置和传入编辑器的markdown源文档
     * Set Markdown source document
     *
     * @param   {String}  md   要传入的markdown源文档
     * @returns {editormd}     返回editormd的实例对象
     */
    setMarkdown: function (md) {
      this.cm.setValue(md || this.settings.markdown);
      return this;
    },

    /**
     * 获取编辑器的markdown源文档
     * Set Editor.md markdown/CodeMirror value
     *
     * @returns {editormd}     返回editormd的实例对象
     */
    getMarkdown: function () {
      return this.cm.getValue();
    },

    /**
     * 获取编辑器的源文档
     * Get CodeMirror value
     *
     * @returns {editormd}     返回editormd的实例对象
     */
    getValue: function () {
      return this.cm.getValue();
    },

    /**
     * 设置编辑器的源文档
     * Set CodeMirror value
     *
     * @param   {String}   value   set code/value/string/text
     * @returns {editormd}       返回editormd的实例对象
     */
    setValue: function (value) {
      this.cm.setValue(value);
      return this;
    },

    /**
     * 清空编辑器
     * Empty CodeMirror editor container
     *
     * @returns {editormd}     返回editormd的实例对象
     */
    clear: function () {
      this.cm.setValue("");
      return this;
    },

    /**
     * 获取解析后存放在Textarea的HTML源码
     * Get parsed html code from Textarea
     *
     * @returns {String}         返回HTML源码
     */
    getHTML: function () {
      if (!this.settings.saveHTMLToTextarea) {
        alert("Error: settings.saveHTMLToTextarea == false");
        return false;
      }
      return this.htmlTextarea.val();
    },

    /**
     * getHTML()的别名
     * getHTML (alias)
     *
     * @returns {String}       Return html code 返回HTML源码
     */

    getTextareaSavedHTML: function () {
      return this.getHTML();
    },

    /**
     * 获取预览窗口的HTML源码
     * Get html from preview container
     *
     * @returns {editormd}     返回editormd的实例对象
     */

    getPreviewedHTML: function () {
      if (!this.settings.watch) {
        alert("Error: settings.watch == false");
        return false;
      }
      return this.previewContainer.html();
    },

    /**
     * 开启实时预览
     * Enable real-time watching
     *
     * @returns {editormd}     返回editormd的实例对象
     */

    watch: function (callback) {
      var settings = this.settings;
      if ($.inArray(settings.mode, ["gfm", "markdown"]) < 0) {
        return this;
      }
      this.state.watching = settings.watch = true;
      this.preview.show();
      if (this.toolbar) {
        var watchIcon = settings.toolbarIconsClass.watch;
        var unWatchIcon = settings.toolbarIconsClass.unwatch;
        var icon = this.toolbar.find(".fa[name=watch]");
        icon.parent().attr("title", settings.lang.toolbar.watch);
        icon.removeClass(unWatchIcon).addClass(watchIcon);
      }
      this.codeMirror
        .css("border-right", "1px solid #ddd")
        .width(this.editor.width() / 2);
      timer = 0;
      this.save().resize();
      if (!settings.onwatch) {
        settings.onwatch = callback || function () {
        };
      }
      $.proxy(settings.onwatch, this)();
      return this;
    },

    /**
     * 关闭实时预览
     * Disable real-time watching
     *
     * @returns {editormd}     返回editormd的实例对象
     */

    unwatch: function (callback) {
      var settings = this.settings;
      this.state.watching = settings.watch = false;
      this.preview.hide();
      if (this.toolbar) {
        var watchIcon = settings.toolbarIconsClass.watch;
        var unWatchIcon = settings.toolbarIconsClass.unwatch;
        var icon = this.toolbar.find(".fa[name=watch]");
        icon.parent().attr("title", settings.lang.toolbar.unwatch);
        icon.removeClass(watchIcon).addClass(unWatchIcon);
      }
      this.codeMirror.css("border-right", "none").width(this.editor.width());
      this.resize();
      if (!settings.onunwatch) {
        settings.onunwatch = callback || function () {
        };
      }
      $.proxy(settings.onunwatch, this)();
      return this;
    },

    /**
     * 显示编辑器
     * Show editor
     *
     * @param   {Function} [callback=function()] 回调函数
     * @returns {editormd}             返回editormd的实例对象
     */

    show: function (callback) {
      callback = callback || function () {
      };
      var _this = this;
      this.editor.show(0, function () {
        $.proxy(callback, _this)();
      });
      return this;
    },

    /**
     * 隐藏编辑器
     * Hide editor
     *
     * @param   {Function} [callback=function()] 回调函数
     * @returns {editormd}             返回editormd的实例对象
     */

    hide: function (callback) {
      callback = callback || function () {
      };
      var _this = this;
      this.editor.hide(0, function () {
        $.proxy(callback, _this)();
      });
      return this;
    },

    /**
     * 隐藏编辑器部分，只预览HTML
     * Enter preview html state
     *
     * @returns {editormd}     返回editormd的实例对象
     */

    previewing: function () {
      var _this = this;
      var editor = this.editor;
      var preview = this.preview;
      var toolbar = this.toolbar;
      var settings = this.settings;
      var codeMirror = this.codeMirror;
      var previewContainer = this.previewContainer;
      if ($.inArray(settings.mode, ["gfm", "markdown"]) < 0) {
        return this;
      }
      if (settings.toolbar && toolbar) {
        toolbar.toggle();
        toolbar.find(".fa[name=preview]").toggleClass("active");
      }
      codeMirror.toggle();
      var escHandle = function (event) {
        if (event.shiftKey && event.keyCode === 27) {
          _this.previewed();
        }
      };
      if (codeMirror.css("display") === "none") {
        // 为了兼容Zepto，而不使用codeMirror.is(":hidden")
        this.state.preview = true;
        if (this.state.fullscreen) {
          preview.css("background", "#fff");
        }
        editor
          .find("." + this.classPrefix + "preview-close-btn")
          .show()
          .bind(editormd.mouseOrTouch("click", "touchend"), function () {
            _this.previewed();
          });
        if (!settings.watch) {
          this.save();
        } else {
          previewContainer.css("padding", "");
        }
        previewContainer.addClass(this.classPrefix + "preview-active");
        preview.show().css({
          position: "",
          top: 0,
          width: editor.width(),
          height:
            settings.autoHeight && !this.state.fullscreen
              ? "auto"
              : editor.height()
        });
        if (this.state.loaded) {
          $.proxy(settings.onpreviewing, this)();
        }
        $(window).bind("keyup", escHandle);
      } else {
        $(window).unbind("keyup", escHandle);
        this.previewed();
      }
    },

    /**
     * 显示编辑器部分，退出只预览HTML
     * Exit preview html state
     *
     * @returns {editormd}     返回editormd的实例对象
     */

    previewed: function () {
      var editor = this.editor;
      var preview = this.preview;
      var toolbar = this.toolbar;
      var settings = this.settings;
      var previewContainer = this.previewContainer;
      var previewCloseBtn = editor.find(
        "." + this.classPrefix + "preview-close-btn"
      );
      this.state.preview = false;
      this.codeMirror.show();
      if (settings.toolbar) {
        toolbar.show();
      }
      preview[settings.watch ? "show" : "hide"]();
      previewCloseBtn.hide().unbind(editormd.mouseOrTouch("click", "touchend"));
      previewContainer.removeClass(this.classPrefix + "preview-active");
      if (settings.watch) {
        previewContainer.css("padding", "20px");
      }
      preview.css({
        background: null,
        position: "absolute",
        width: editor.width() / 2,
        height:
          settings.autoHeight && !this.state.fullscreen
            ? "auto"
            : editor.height() - toolbar.height(),
        top: settings.toolbar ? toolbar.height() : 0
      });
      if (this.state.loaded) {
        $.proxy(settings.onpreviewed, this)();
      }
      return this;
    },

    /**
     * 编辑器全屏显示
     * Fullscreen show
     *
     * @returns {editormd}     返回editormd的实例对象
     */

    fullscreen: function () {
      var _this = this;
      var state = this.state;
      var editor = this.editor;
      var toolbar = this.toolbar;
      var settings = this.settings;
      var fullscreenClass = this.classPrefix + "fullscreen";
      if (toolbar) {
        toolbar
          .find(".fa[name=fullscreen]")
          .parent()
          .toggleClass("active");
      }
      var escHandle = function (event) {
        if (!event.shiftKey && event.keyCode === 27) {
          if (state.fullscreen) {
            _this.fullscreenExit();
          }
        }
      };
      if (!editor.hasClass(fullscreenClass)) {
        state.fullscreen = true;
        $("html,body").css("overflow", "hidden");
        editor
          .css({
            width: $(window).width(),
            height: $(window).height()
          })
          .addClass(fullscreenClass);
        this.resize();
        $.proxy(settings.onfullscreen, this)();
        $(window).bind("keyup", escHandle);
      } else {
        $(window).unbind("keyup", escHandle);
        this.fullscreenExit();
      }
      return this;
    },

    /**
     * 编辑器退出全屏显示
     * Exit fullscreen state
     *
     * @returns {editormd}     返回editormd的实例对象
     */

    fullscreenExit: function () {
      var editor = this.editor;
      var settings = this.settings;
      var toolbar = this.toolbar;
      var fullscreenClass = this.classPrefix + "fullscreen";
      this.state.fullscreen = false;
      if (toolbar) {
        toolbar
          .find(".fa[name=fullscreen]")
          .parent()
          .removeClass("active");
      }
      $("html,body").css("overflow", "");
      editor
        .css({
          width: editor.data("oldWidth"),
          height: editor.data("oldHeight")
        })
        .removeClass(fullscreenClass);
      this.resize();
      $.proxy(settings.onfullscreenExit, this)();
      return this;
    },

    /**
     * 加载并执行插件
     * Load and execute the plugin
     *
     * @param   {String}   name  plugin name / function name
     * @param   {String}   path  plugin load path
     * @returns {editormd}       返回editormd的实例对象
     */

    executePlugin: function (name, path) {
      var _this = this;
      var cm = this.cm;
      var settings = this.settings;
      path = settings.pluginPath + path;
      // if (typeof define === "function") {
      //   if (typeof this[name] === "undefined") {
      //     alert(
      //       "Error: " +
      //       name +
      //       " plugin is not found, you are not load this plugin."
      //     );
      //     return this;
      //   }
      //   this[name](cm);
      //   return this;
      // }
      if ($.inArray(path, editormd.loadFiles.plugin) < 0) {
        editormd.loadPlugin(path, function () {
          editormd.loadPlugins[name] = _this[name];
          _this[name](cm);
        });
      } else {
        $.proxy(editormd.loadPlugins[name], this)(cm);
      }
      return this;
    },

    /**
     * 搜索替换
     * Search & replace
     *
     * @param   {String}   command  CodeMirror serach commands, "find, fintNext, fintPrev, clearSearch, replace, replaceAll"
     * @returns {editormd}        return this
     */

    search: function (command) {
      var settings = this.settings;
      if (!settings.searchReplace) {
        alert("Error: settings.searchReplace == false");
        return this;
      }
      if (!settings.readOnly) {
        this.cm.execCommand(command || "find");
      }
      return this;
    },
    searchReplace: function () {
      this.search("replace");
      return this;
    },
    searchReplaceAll: function () {
      this.search("replaceAll");
      return this;
    }
  };

  editormd.fn.init.prototype = editormd.fn;

  /**
   * 锁屏
   * lock screen when dialog opening
   *
   * @returns {void}
   */
  editormd.dialogLockScreen = function () {
    var settings = this.settings || {dialogLockScreen: true};
    if (settings.dialogLockScreen) {
      $("html,body").css("overflow", "hidden");
      this.resize();
    }
  };

  /**
   * 显示透明背景层
   * Display mask layer when dialog opening
   *
   * @param   {Object}   dialog  dialog jQuery object
   * @returns {void}
   */
  editormd.dialogShowMask = function (dialog) {
    var editor = this.editor;
    var settings = this.settings || {dialogShowMask: true};
    dialog.css({
      top: ($(window).height() - dialog.height()) / 2 + "px",
      left: ($(window).width() - dialog.width()) / 2 + "px"
    });
    if (settings.dialogShowMask) {
      editor
        .children("." + this.classPrefix + "mask")
        .css("z-index", parseInt(dialog.css("z-index")) - 1)
        .show();
    }
  };

  editormd.toolbarHandlers = {
    undo: function () {
      this.cm.undo();
    },
    redo: function () {
      this.cm.redo();
    },
    bold: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      cm.replaceSelection("**" + selection + "**");
      if (selection === "") {
        cm.setCursor(cursor.line, cursor.ch + 2);
      }
    },
    del: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      cm.replaceSelection("~~" + selection + "~~");
      if (selection === "") {
        cm.setCursor(cursor.line, cursor.ch + 2);
      }
    },
    italic: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      cm.replaceSelection("*" + selection + "*");
      if (selection === "") {
        cm.setCursor(cursor.line, cursor.ch + 1);
      }
    },
    quote: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      if (cursor.ch !== 0) {
        cm.setCursor(cursor.line, 0);
        cm.replaceSelection("> " + selection);
        cm.setCursor(cursor.line, cursor.ch + 2);
      } else {
        cm.replaceSelection("> " + selection);
      }
      //cm.replaceSelection("> " + selection);
      //cm.setCursor(cursor.line, (selection === "") ? cursor.ch + 2 : cursor.ch + selection.length + 2);
    },
    ucfirst: function () {
      var cm = this.cm;
      var selection = cm.getSelection();
      var selections = cm.listSelections();
      cm.replaceSelection(editormd.firstUpperCase(selection));
      cm.setSelections(selections);
    },
    ucwords: function () {
      var cm = this.cm;
      var selection = cm.getSelection();
      var selections = cm.listSelections();
      cm.replaceSelection(editormd.wordsFirstUpperCase(selection));
      cm.setSelections(selections);
    },
    uppercase: function () {
      var cm = this.cm;
      var selection = cm.getSelection();
      var selections = cm.listSelections();
      cm.replaceSelection(selection.toUpperCase());
      cm.setSelections(selections);
    },
    lowercase: function () {
      var cm = this.cm;
      var selection = cm.getSelection();
      var selections = cm.listSelections();
      cm.replaceSelection(selection.toLowerCase());
      cm.setSelections(selections);
    },
    h1: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      if (cursor.ch !== 0) {
        cm.setCursor(cursor.line, 0);
        cm.replaceSelection("# " + selection);
        cm.setCursor(cursor.line, cursor.ch + 2);
      } else {
        cm.replaceSelection("# " + selection);
      }
    },
    h2: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      if (cursor.ch !== 0) {
        cm.setCursor(cursor.line, 0);
        cm.replaceSelection("## " + selection);
        cm.setCursor(cursor.line, cursor.ch + 3);
      } else {
        cm.replaceSelection("## " + selection);
      }
    },
    h3: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      if (cursor.ch !== 0) {
        cm.setCursor(cursor.line, 0);
        cm.replaceSelection("### " + selection);
        cm.setCursor(cursor.line, cursor.ch + 4);
      } else {
        cm.replaceSelection("### " + selection);
      }
    },
    h4: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      if (cursor.ch !== 0) {
        cm.setCursor(cursor.line, 0);
        cm.replaceSelection("#### " + selection);
        cm.setCursor(cursor.line, cursor.ch + 5);
      } else {
        cm.replaceSelection("#### " + selection);
      }
    },
    h5: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      if (cursor.ch !== 0) {
        cm.setCursor(cursor.line, 0);
        cm.replaceSelection("##### " + selection);
        cm.setCursor(cursor.line, cursor.ch + 6);
      } else {
        cm.replaceSelection("##### " + selection);
      }
    },
    h6: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      if (cursor.ch !== 0) {
        cm.setCursor(cursor.line, 0);
        cm.replaceSelection("###### " + selection);
        cm.setCursor(cursor.line, cursor.ch + 7);
      } else {
        cm.replaceSelection("###### " + selection);
      }
    },
    "list-ul": function () {
      var cm = this.cm;
      var selection = cm.getSelection();
      if (selection === "") {
        cm.replaceSelection("- " + selection);
      } else {
        var selectionText = selection.split("\n");
        for (var i = 0, len = selectionText.length; i < len; i++) {
          selectionText[i] =
            selectionText[i] === "" ? "" : "- " + selectionText[i];
        }
        cm.replaceSelection(selectionText.join("\n"));
      }
    },
    "list-ol": function () {
      var cm = this.cm;
      var selection = cm.getSelection();
      if (selection === "") {
        cm.replaceSelection("1. " + selection);
      } else {
        var selectionText = selection.split("\n");
        for (var i = 0, len = selectionText.length; i < len; i++) {
          selectionText[i] =
            selectionText[i] === "" ? "" : i + 1 + ". " + selectionText[i];
        }
        cm.replaceSelection(selectionText.join("\n"));
      }
    },
    hr: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      cm.replaceSelection(
        (cursor.ch !== 0 ? "\n\n" : "\n") + "------------\n\n"
      );
    },
    tex: function () {
      if (!this.settings.tex) {
        alert("settings.tex === false");
        return this;
      }
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      cm.replaceSelection("$$" + selection + "$$");
      if (selection === "") {
        cm.setCursor(cursor.line, cursor.ch + 2);
      }
    },
    link: function () {
      this.executePlugin("linkDialog", "link-dialog/link-dialog");
    },
    "reference-link": function () {
      this.executePlugin(
        "referenceLinkDialog",
        "reference-link-dialog/reference-link-dialog"
      );
    },
    pagebreak: function () {
      if (!this.settings.pageBreak) {
        alert("settings.pageBreak === false");
        return this;
      }
      var cm = this.cm;
      cm.replaceSelection("\r\n<!--nextpage-->\r\n");
    },
    more: function () {
      if (!this.settings.more) {
        alert("settings.more === false");
        return this;
      }
      var cm = this.cm;
      cm.replaceSelection("\r\n<!--more-->\r\n");
    },
    image: function () {
      this.executePlugin("imageDialog", "image-dialog/image-dialog");
    },
    code: function () {
      var cm = this.cm;
      var cursor = cm.getCursor();
      var selection = cm.getSelection();
      cm.replaceSelection("`" + selection + "`");
      if (selection === "") {
        cm.setCursor(cursor.line, cursor.ch + 1);
      }
    },
    "code-block": function () {
      this.executePlugin(
        "codeBlockDialog",
        "code-block-dialog/code-block-dialog"
      );
    },
    "preformatted-text": function () {
      this.executePlugin(
        "preformattedTextDialog",
        "preformatted-text-dialog/preformatted-text-dialog"
      );
    },
    table: function () {
      this.executePlugin("tableDialog", "table-dialog/table-dialog");
    },
    datetime: function () {
      var cm = this.cm;
      var langName = this.settings.lang.name;
      var datefmt =
        editormd.dateFormat() +
        " " +
        editormd.dateFormat(
          langName === "zh-cn" || langName === "zh-tw"
            ? "cn-week-day"
            : "week-day"
        );
      cm.replaceSelection(datefmt);
    },
    emoji: function () {
      this.executePlugin("emojiDialog", "emoji-dialog/emoji-dialog");
    },
    "html-entities": function () {
      this.executePlugin(
        "htmlEntitiesDialog",
        "html-entities-dialog/html-entities-dialog"
      );
    },
    "goto-line": function () {
      this.executePlugin("gotoLineDialog", "goto-line-dialog/goto-line-dialog");
    },
    watch: function () {
      this[this.settings.watch ? "unwatch" : "watch"]();
    },
    preview: function () {
      this.previewing();
    },
    fullscreen: function () {
      this.fullscreen();
    },
    clear: function () {
      this.clear();
    },
    search: function () {
      this.search();
    },
    help: function () {
      this.executePlugin("helpDialog", "help-dialog/help-dialog");
    },
    info: function () {
      this.showInfoDialog();
    }
  };

  var isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
  var key = isMac ? "Cmd" : "Ctrl";
  var _editormd$keyMaps;

  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }
    return obj;
  }

  editormd.keyMaps = ((_editormd$keyMaps = {}),
  _defineProperty(_editormd$keyMaps, key + "-1", "h1"),
  _defineProperty(_editormd$keyMaps, key + "-2", "h2"),
  _defineProperty(_editormd$keyMaps, key + "-3", "h3"),
  _defineProperty(_editormd$keyMaps, key + "-4", "h4"),
  _defineProperty(_editormd$keyMaps, key + "-5", "h5"),
  _defineProperty(_editormd$keyMaps, key + "-6", "h6"),
  _defineProperty(_editormd$keyMaps, key + "-B", "bold"),
  _defineProperty(_editormd$keyMaps, key + "-D", "datetime"),
  _defineProperty(_editormd$keyMaps, key + "-E", function () {
    // emoji
    var cm = this.cm;
    var cursor = cm.getCursor();
    var selection = cm.getSelection();
    if (!this.settings.emoji) {
      alert("Error: settings.emoji == false");
      return;
    }
    cm.replaceSelection(":" + selection + ":");
    if (selection === "") {
      cm.setCursor(cursor.line, cursor.ch + 1);
    }
  }),
  _defineProperty(_editormd$keyMaps, key + "-Alt-G", "goto-line"),
  _defineProperty(_editormd$keyMaps, key + "-H", "hr"),
  _defineProperty(_editormd$keyMaps, key + "-I", "italic"),
  _defineProperty(_editormd$keyMaps, key + "-K", "code"),
  _defineProperty(_editormd$keyMaps, "Ctrl-L", function CtrlL() {
    var cm = this.cm;
    var cursor = cm.getCursor();
    var selection = cm.getSelection();
    var title = selection === "" ? "" : " \"" + selection + "\"";
    cm.replaceSelection("[" + selection + "](" + title + ")");
    if (selection === "") {
      cm.setCursor(cursor.line, cursor.ch + 1);
    }
  }),
  _defineProperty(_editormd$keyMaps, key + "-U", "list-ul"),
  _defineProperty(_editormd$keyMaps, "Shift-Ctrl-A", function ShiftCtrlA() {
    var cm = this.cm;
    var cursor = cm.getCursor();
    var selection = cm.getSelection();
    if (!this.settings.atLink) {
      alert("Error: settings.atLink == false");
      return;
    }
    cm.replaceSelection("@" + selection);
    if (selection === "") {
      cm.setCursor(cursor.line, cursor.ch + 1);
    }
  }),
  _defineProperty(_editormd$keyMaps, "Shift" + key + "-C", "code"),
  _defineProperty(_editormd$keyMaps, "Shift" + key + "Q", "quote"),
  _defineProperty(_editormd$keyMaps, "Shift" + key + "S", "del"),
  _defineProperty(_editormd$keyMaps, "Shift" + key + "K", "tex"),
  _defineProperty(_editormd$keyMaps, "Shift-Alt-C", function ShiftAltC() {
    var cm = this.cm;
    var cursor = cm.getCursor();
    var selection = cm.getSelection();
    cm.replaceSelection(["```", selection, "```"].join("\n"));
    if (selection === "") {
      cm.setCursor(cursor.line, cursor.ch + 3);
    }
  }),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-Alt-C", "code-block"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-H", "html-entities"),
  _defineProperty(_editormd$keyMaps, "Shift-Alt-H", "help"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-E", "emoji"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-U", "uppercase"),
  _defineProperty(_editormd$keyMaps, "Shift-Alt-U", "ucwords"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-Alt-U", "ucfirst"),
  _defineProperty(_editormd$keyMaps, "Shift-Alt-L", "lowercase"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-I", function () {
    var cm = this.cm;
    var cursor = cm.getCursor();
    var selection = cm.getSelection();
    var title = selection === "" ? "" : " \"" + selection + "\"";
    cm.replaceSelection("![" + selection + "](" + title + ")");
    if (selection === "") {
      cm.setCursor(cursor.line, cursor.ch + 4);
    }
  }),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-Alt-I", "image"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-L", "link"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-O", "list-ol"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-P", "preformatted-text"),
  _defineProperty(_editormd$keyMaps, "Shift-" + key + "-T", "table"),
  _defineProperty(_editormd$keyMaps, "Shift-Alt-P", "pagebreak"),
  _defineProperty(_editormd$keyMaps, "Shift-Alt-M", "more"),
  _defineProperty(_editormd$keyMaps, "F9", "watch"),
  _defineProperty(_editormd$keyMaps, "F10", "preview"),
  _defineProperty(_editormd$keyMaps, "F11", "fullscreen"),
  _editormd$keyMaps
  );


  /**
   * 清除字符串两边的空格
   * Clear the space of strings both sides.
   *
   * @param   {String}  str      string
   * @returns {String}           trimed string
   */
  var trim = function (str) {
    return !String.prototype.trim
      ? str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "")
      : str.trim();
  };

  editormd.trim = trim;

  /**
   * 所有单词首字母大写
   * Words first to uppercase
   *
   * @param   {String}  str      string
   * @returns {String}           string
   */
  var ucwords = function (str) {
    return str.toLowerCase().replace(/\b(\w)|\s(\w)/g, function ($1) {
      return $1.toUpperCase();
    });
  };

  editormd.ucwords = editormd.wordsFirstUpperCase = ucwords;

  /**
   * 字符串首字母大写
   * Only string first char to uppercase
   *
   * @param   {String}  str      string
   * @returns {String}           string
   */
  var firstUpperCase = function (str) {
    return str.toLowerCase().replace(/\b(\w)/, function ($1) {
      return $1.toUpperCase();
    });
  };

  editormd.firstUpperCase = editormd.ucfirst = firstUpperCase;
  editormd.urls = {
    atLinkBase: "https://github.com/"
  };
  editormd.regexs = {
    // @LuRenJiasWorld 2020-06-01
    // highLightText这里本来应该使用类似/(?<![=])(==)(?![=])/的Look behind/forward正则表达式扩展
    // 但是Look behind assertion直到ES2018才受支持，依旧有大量浏览器未能实现这一标准
    // 因此只能使用暴力的方法进行匹配，缺点是对于===aaa===这样的错误语句识别会有问题
    // 好在参考了一下其他实现，Typora等编辑器也是使用类似的方法实现的匹配，就无所谓了XD
    highlightText: /==([\S\s]+?)==/g,
    atLink: /@(\w+)/g,
    email: /(\w+)@(\w+)\.(\w+)\.?(\w+)?/g,
    emailLink: /(mailto:)?([\w\.\_]+)@(\w+)\.(\w+)\.?(\w+)?/g,
    emoji: /:([\w\+-]+):/g,
    emojiDatetime: /(\d{1,2}:\d{1,2}:\d{1,2})/g,
    twemoji: /:(tw-([\w]+)-?(\w+)?):/g,
    fontAwesome: /:(fa-([\w]+)(-(\w+)){0,}):/g,
    editormdLogo: /:(editormd-logo-?(\w+)?):/g,
    pageBreak: /^<!--nextpage-->/mg,
    more: /^<!--more-->/mg,
    url: /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g
  };
  // Emoji graphics files url path
  editormd.emoji = {
    path: "//cdn.jsdelivr.net/npm/emojify.js@1.1.0/dist/images/basic/",
    ext: ".png"
  };
  // Twitter Emoji (Twemoji)  graphics files url path
  editormd.twemoji = {
    // path: "//cdn.jsdelivr.net/npm/twemoji@2.5.0/2/svg/",////cdn.bootcss.com/twemoji/2.5.1/2/svg/
    // ext: ".svg"
    path: "//cdnjs.cloudflare.com/ajax/libs/twemoji/2.6.0/36x36/",
    ext: ".png"
  };

  /**
   * 自定义marked的解析器
   * Custom Marked renderer rules
   *
   * @param   {Array}  markdownToC   传入用于接收TOC的数组
   * @returns {Renderer} markedRenderer  返回marked的Renderer自定义对象
   */
  editormd.markedRenderer = function (markdownToC, options) {
    var defaults = {
      toc: true, // Table of contents
      tocm: false,
      tocStartLevel: 1, // Said from H1 to create ToC
      pageBreak: true,
      more: true,
      atLink: true, // for @link
      emailLink: true, // for mail address auto link
      taskList: false, // Enable Github Flavored Markdown task lists
      emoji: false, // :emoji: , Support Twemoji, fontAwesome, Editor.md logo emojis.
      tex: false, // TeX(LaTeX), based on KaTeX
    };
    var settings = $.extend(defaults, options || {});
    var marked = editormd.$marked;
    var markedRenderer = new marked.Renderer();
    markdownToC = markdownToC || [];
    var regexs = editormd.regexs;
    var atLinkReg = regexs.atLink;
    var emojiReg = regexs.emoji;
    var emailReg = regexs.email;
    var emailLinkReg = regexs.emailLink;
    var twemojiReg = regexs.twemoji;
    var faIconReg = regexs.fontAwesome;
    var editormdLogoReg = regexs.editormdLogo;
    var pageBreakReg = /&lt;!--nextpage--&gt;/mg;
    var moreReg = /&lt;!--more--&gt;/mg;

    markedRenderer.emoji = function (text) {
      text = text.replace(editormd.regexs.emojiDatetime, function ($1) {
        return $1.replace(/:/g, "&#58;");
      });

      var matchs = text.match(emojiReg);

      if (!matchs || !settings.emoji) {
        return text;
      }

      for (var i = 0, len = matchs.length; i < len; i++) {
        if (matchs[i] === ":+1:") {
          matchs[i] = ":\\+1:";
        }

        text = text.replace(new RegExp(matchs[i]), function ($1) {
          var faMatchs = $1.match(faIconReg);
          var name = $1.replace(/:/g, "");
          if (faMatchs) {
            for (var fa = 0, len1 = faMatchs.length; fa < len1; fa++) {
              var faName = faMatchs[fa].replace(/:/g, "");
              return (
                "<i class=\"fa " +
                faName +
                " fa-emoji\" title=\"" +
                faName.replace("fa-", "") +
                "\"></i>"
              );
            }
          } else {
            var emdlogoMathcs = $1.match(editormdLogoReg);
            var twemojiMatchs = $1.match(twemojiReg);
            if (emdlogoMathcs) {
              for (var x = 0, len2 = emdlogoMathcs.length; x < len2; x++) {
                var logoName = emdlogoMathcs[x].replace(/:/g, "");
                return (
                  "<i class=\"" +
                  logoName +
                  "\" title=\"Editor.md logo (" +
                  logoName +
                  ")\"></i>"
                );
              }
            } else if (twemojiMatchs) {
              for (var t = 0, len3 = twemojiMatchs.length; t < len3; t++) {
                var twe = twemojiMatchs[t].replace(/:/g, "").replace("tw-", "");
                return (
                  "<img src=\"" +
                  editormd.twemoji.path +
                  twe +
                  editormd.twemoji.ext +
                  "\" title=\"twemoji-" +
                  twe +
                  "\" alt=\"twemoji-" +
                  twe +
                  "\" class=\"emoji twemoji\" />"
                );
              }
            } else {
              var src = name === "+1" ? "plus1" : name;
              src = src === "black_large_square" ? "black_square" : src;
              src = src === "moon" ? "waxing_gibbous_moon" : src;
              return (
                "<img src=\"" +
                editormd.emoji.path +
                src +
                editormd.emoji.ext +
                "\" class=\"emoji\" title=\"&#58;" +
                name +
                "&#58;\" alt=\"&#58;" +
                name +
                "&#58;\" />"
              );
            }
          }
        });
      }
      return text;
    };

    markedRenderer.atLink = function (text) {
      if (atLinkReg.test(text)) {
        if (settings.atLink) {
          text = text.replace(emailReg, function ($1) {
            return $1.replace(/@/g, "_#_&#64;_#_");
          });
          text = text
            .replace(atLinkReg, function ($1, $2) {
              return (
                "<a href=\"" +
                editormd.urls.atLinkBase +
                "" +
                $2 +
                "\" title=\"&#64;" +
                $2 +
                "\" class=\"at-link\">" +
                $1 +
                "</a>"
              );
            })
            .replace(/_#_&#64;_#_/g, "@");
        }
        if (settings.emailLink) {
          text = text.replace(emailLinkReg, function ($1, $2, $3, $4, $5) {
            return !$2 &&
            $.inArray($5, "jpg|jpeg|png|gif|webp|ico|icon|pdf".split("|")) < 0
              //? "<a href=\"mailto:" + $1 + "\">" + $1 + "</a>"
              ? "mailto:" + $1
              : $1;
          });
        }
        return text;
      }
      return text;
    };

    markedRenderer.link = function (href, title, text) {
      if (this.options.sanitize) {
        try {
          var prot = decodeURIComponent(unescape(href))
            .replace(/[^\w:]/g, "")
            .toLowerCase();
          if (prot.indexOf("javascript:") === 0) {
            return "";
          }
        } catch (e) {
          return "";
        }
      }
      var out = "<a target=\"_blank\" href=\"" + href + "\"";
      if (atLinkReg.test(title) || atLinkReg.test(text)) {
        if (title) {
          out += " title=\"" + title.replace(/@/g, "&#64;");
        }
        return out + "\">" + text.replace(/@/g, "&#64;") + "</a>";
      }
      if (title) {
        out += " title=\"" + title + "\"";
      }
      out += ">" + text + "</a>";
      return out;
    };

    markedRenderer.heading = function (text, level) {
      var linkText = text;
      var hasLinkReg = /\s*<a\s*href\=\"(.*)\"\s*([^\>]*)\>(.*)<\/a\>\s*/;
      var getIdClassReg = /\{(.+?)\}/g;

      text = text.replace(getIdClassReg, "");

      if (hasLinkReg.test(text)) {
        var tempText = [];
        text = text.split(/<a\s*([^\>]+)\>([^\>]*)<\/a\>/);
        for (var i = 0, len = text.length; i < len; i++) {
          tempText.push(text[i].replace(/\s*href\=\"(.*)\"\s*/g, ""));
        }
        text = tempText.join(" ");
      }
      text = trim(text);
      var escapedText = text.toLowerCase().replace(/[^\w]+/g, "-");
      var toc = {
        text: text,
        level: level,
        slug: escapedText
      };
      var isChinese = /^[\u4e00-\u9fa5]+$/.test(text);
      var id = isChinese
        ? escape(text).replace(/\%/g, "")
        : text.toLowerCase().replace(/[^\w]+/g, "-");
      markdownToC.push(toc);
      var headingHTML =
        "<h" +
        level +
        " id=\"h" +
        level +
        "-" +
        this.options.headerPrefix +
        id +
        "\">";
      //headingHTML += "<a name=\"" + text.replace(/<[^>]*>\s?/g,'') + "\" class=\"reference-link\"></a>";
      headingHTML += "<a name=\"" + text + "\" class=\"reference-link\"></a>";
      headingHTML += "<span class=\"header-link octicon octicon-link\"></span>";
      headingHTML += hasLinkReg
        ? this.atLink(this.emoji(linkText)).replace(getIdClassReg, "")
        : this.atLink(this.emoji(text)).replace(getIdClassReg, "");
      headingHTML += "</h" + level + ">";
      return headingHTML;
    };

    markedRenderer.pageBreak = function (text) {
      pageBreakReg.test(text);
      if (pageBreakReg.test(text) && settings.pageBreak) {

        text = "<hr style=\"page-break-after:always;\" class=\"page-break editormd-page-break\" />";
      }
      return text;
    };

    markedRenderer.more = function (text) {
      moreReg.test(text);
      if (moreReg.test(text) && settings.more) {
        text = "<p><img class=\"editormd-more more\" src=\"\"></p>";
      }
      return text;
    };

    markedRenderer.paragraph = function (text) {
      //text = text.replace("<em>","_").replace("</em>","_");  //在如果公式含有_则会被Markdown解析，所以现在需要转换过来
      var isTeXInline = /\$(.*)\$/g.test(text); //行内公式
      var isTeXLine = /^\$\$(.*)\$\$$/.test(text); //多行公式
      var isTeXAddClass = isTeXLine ? " class=\"" + editormd.classNames.block_tex + "\"" : "";
      var isToC = settings.tocm
        ? /^(\[TOC\]|\[TOCM\])$/i.test(text)
        : /^\[TOC\]$/i.test(text);
      var isToCMenu = /^\[TOCM\]$/i.test(text);
      if (!isTeXLine && isTeXInline) {
        text = text.replace(/\\<br>+/g, "\\\\<br>"); //只能用于换行（特征：/<br>）
        text = text.replace(/(\$([^\$]*)\$)+/g, function ($1, $2) {
          return (
            "<span class=\"" +
            editormd.classNames.inline_tex +
            "\">" +
            $2.replace(/\$/g, "").replace("<em>", "_").replace("</em>", "_") +
            "</span>"
          );
        });
      } else {
        text = text.replace(/\\<br>+/g, "\\\\<br>"); //只能用于换行（特征：/<br>）
        text = isTeXLine ? text.replace(/\$/g, "").replace("<em>", "_").replace("</em>", "_") : text;
      }
      var tocHTML = "<div class=\"markdown-toc editormd-markdown-toc\">" + text + "</div>";

      return isToC ? isToCMenu ? "<div class=\"editormd-toc-menu\">" + tocHTML + "</div><br/>"
        : tocHTML
        : pageBreakReg.test(text)
          ? this.pageBreak(text)
          : moreReg.test(text)
            ? this.more(text)
            : "<p" +
          isTeXAddClass +
          ">" +
          this.atLink(this.emoji(text)) +
          "</p>\n";
    };

    markedRenderer.code = function (code, lang) {
      if (lang === "mermaid") {
        return "<div class='mermaid'>" + code + "</div>";
      } else if (lang === "mind") {
        return "<div class=\"mind\" style=\"width: 100%;overflow-x: auto;\"><canvas></canvas><div class=\"mindTxt\">" + code + "</div></div>";
      } else if (lang === "math" || lang === "latex" || lang === "katex") {
        return "<p class=\"" + editormd.classNames.block_tex + "\">" + code + "</p>";
      } else {
        return marked.Renderer.prototype.code.apply(this, arguments);
      }
    };

    markedRenderer.tablecell = function (content, flags) {
      var type = flags.header ? "th" : "td";
      var tag = flags.align
        ? "<" + type + " style=\"text-align:" + flags.align + "\">"
        : "<" + type + ">";
      return tag + this.paragraph(this.atLink(this.emoji(content))) + "</" + type + ">\n";
    };

    markedRenderer.listitem = function (text) {
      if (settings.taskList && /^\s*\[[x\s]\]\s*/.test(text)) {
        text = text
          .replace(
            /^\s*\[\s\]\s*/,
            "<input type=\"checkbox\" class=\"task-list-item-checkbox\" /> "
          )
          .replace(
            /^\s*\[x\]\s*/,
            "<input type=\"checkbox\" class=\"task-list-item-checkbox\" checked disabled /> "
          );
        return (
          "<li style=\"list-style: none;\">" +
          this.atLink(this.emoji(text)) +
          "</li>"
        );
      } else {
        return "<li>" + this.paragraph(this.atLink(this.emoji(text))) + "</li>";
      }
    };
    return markedRenderer;
  };

  /**
   *
   * 生成TOC(Table of Contents)
   * Creating ToC (Table of Contents)
   *
   * @param   {Array}  toc       从marked获取的TOC数组列表
   * @param   {Element}  container     插入TOC的容器元素
   * @param   {Integer}  startLevel    Hx 起始层级
   * @returns {Object}   tocContainer  返回ToC列表容器层的jQuery对象元素
   */
  editormd.markdownToCRenderer = function (toc, container, tocDropdown, startLevel) {
    var html = "";
    var lastLevelArr = [0];
    // var lastLevel = 0
    var classPrefix = this.classPrefix;
    startLevel = startLevel || 1;
    for (var i = 0, len = toc.length; i < len; i++) {
      var text = toc[i].text;
      var level = toc[i].level;
      var stageLen = lastLevelArr.length;
      var lastLevel = lastLevelArr[stageLen - 1];
      var goBack = 0;
      if (level < startLevel) {
        continue;
      }
      if (level > lastLevel) {
        html += "";
        lastLevelArr.push(level);
      } else if (level < lastLevel) {
        for (var j = stageLen - 1; j >= 0; j--) {
          if (level <= lastLevelArr[j]) {
            goBack++;
            lastLevelArr.pop();
          } else {
            lastLevelArr.push(level);
            break;
          }
        }
        html += new Array(goBack + 1).join("</ul></li>");
      } else {
        html += "</ul></li>";
      }
      html +=
        "<li><a class=\"toc-level-" +
        level +
        "\" href=\"#" +
        text +
        "\" level=\"" +
        level +
        "\">" +
        text +
        "</a><ul>";
    }

    var tocContainer = container.find(".markdown-toc");

    if (
      tocContainer.length < 1 &&
      container.attr("previewContainer") === "false"
    ) {
      var tocHTML =
        "<div class=\"markdown-toc " + classPrefix + "markdown-toc\"></div>";
      tocHTML = tocDropdown
        ? "<div class=\"" + classPrefix + "toc-menu\">" + tocHTML + "</div>"
        : tocHTML;
      container.html(tocHTML);
      tocContainer = container.find(".markdown-toc");
    }
    if (tocDropdown) {
      tocContainer.wrap("<div class=\"" + classPrefix + "toc-menu\"></div><br/>");
    }
    tocContainer
      .html("<ul class=\"markdown-toc-list\"></ul>")
      .children(".markdown-toc-list")
      .html(html.replace(/\r?\n?<ul\><\/ul\>/g, ""));
    return tocContainer;
  };

  /**
   *
   * 生成TOC下拉菜单
   * Creating ToC dropdown menu
   *
   * @param   {Object}   container     插入TOC的容器jQuery对象元素
   * @param   {String}   tocTitle    ToC title
   * @returns {Object}           return toc-menu object
   */
  editormd.tocDropdownMenu = function (container, tocTitle) {
    tocTitle = tocTitle || "Table of Contents";
    var zindex = 400;
    var tocMenus = container.find("." + this.classPrefix + "toc-menu");
    tocMenus.each(function () {
      var $this = $(this);
      var toc = $this.children(".markdown-toc");
      var icon = "<i class=\"fa fa-angle-down\"></i>";
      var btn =
        "<a href=\"javascript:;\" class=\"toc-menu-btn\">" +
        icon +
        tocTitle +
        "</a>";
      var menu = toc.children("ul");
      var list = menu.find("li");
      toc.append(btn);
      list.first().before("<li><h1>" + tocTitle + " " + icon + "</h1></li>");
      $this
        .mouseover(function () {
          menu.show();
          list.each(function () {
            var li = $(this);
            var ul = li.children("ul");
            if (ul.html() === "") {
              ul.remove();
            }
            if (ul.length > 0 && ul.html() !== "") {
              var firstA = li.children("a").first();
              if (firstA.children(".fa").length < 1) {
                firstA.append(
                  $(icon).css({float: "right", paddingTop: "4px"})
                );
              }
            }
            li
              .mouseover(function () {
                ul.css("z-index", zindex).show();
                zindex += 1;
              })
              .mouseleave(function () {
                ul.hide();
              });
          });
        })
        .mouseleave(function () {
          menu.hide();
        });
    });

    return tocMenus;
  };

  /**
   * 简单地过滤指定的HTML标签
   * Filter custom html tags
   *
   * @param   {String}   html      要过滤HTML
   * @param   {String}   filters     要过滤的标签
   * @returns {String}   html      返回过滤的HTML
   */
  editormd.filterHTMLTags = function (html, filters) {
    if (typeof html !== "string") {
      html = String(html);
    }
    if (typeof filters !== "string") {
      return html;
    }
    var expression = filters.split("|");
    var filterTags = expression[0].split(",");
    var attrs = expression[1];
    for (var i = 0, len = filterTags.length; i < len; i++) {
      var tag = filterTags[i];
      html = html.replace(
        new RegExp("<s*" + tag + "s*([^>]*)>([^>]*)<s*/" + tag + "s*>", "igm"),
        ""
      );
    }
    //return html;
    if (typeof attrs !== "undefined") {
      var htmlTagRegex = /<(\w+)\s*([^\>]*)\>([^\>]*)<\/(\w+)\>/gi;
      if (attrs === "*") {
        html = html.replace(htmlTagRegex, function ($1, $2, $3, $4, $5) {
          return "<" + $2 + ">" + $4 + "</" + $5 + ">";
        });
      } else if (attrs === "on*") {
        html = html.replace(htmlTagRegex, function ($1, $2, $3, $4, $5) {
          var el = $("<" + $2 + ">" + $4 + "</" + $5 + ">");
          var _attrs = $($1)[0].attributes;
          var $attrs = {};
          $.each(_attrs, function (i, e) {
            if (e.nodeName !== "\"") {
              $attrs[e.nodeName] = e.nodeValue;
              //   fixed <a href="javascript:alert('xss')"> will cause xss problem
              if (e.nodeName === "href" && e.nodeValue.toLowerCase().indexOf("javascript:") >= 0) {
                $attrs[e.nodeName] = "javascript:;";
              }
            }
          });
          $.each($attrs, function (i) {
            if (i.indexOf("on") === 0) {
              delete $attrs[i];
            }
          });
          el.attr($attrs);
          var text = typeof el[1] !== "undefined" ? $(el[1]).text() : "";
          // FIXED 使用 on* 过滤标签的属性，图片加链接的语法会出错的问题
          if ($2 && !isNaN($2) && $2 !== $5) {
            text += "<" + $2 + ">" + $4 + "</" + $5 + ">";
          } else if ($2 && isNaN($2) && $2 !== $5) {
            text += "</" + $5 + ">";
            return el[0].outerHTML + text;
          } else {
            return el[0].outerHTML + text;
          }
        });
      } else {
        html = html.replace(htmlTagRegex, function ($1, $2, $3, $4) {
          var filterAttrs = attrs.split(",");
          var el = $($1);
          el.html($4);
          $.each(filterAttrs, function (i) {
            el.attr(filterAttrs[i], null);
          });
          return el[0].outerHTML;
        });
      }
    }

    return html;
  };

  /**
   * 将Markdown文档解析为HTML用于前台显示
   * Parse Markdown to HTML for Font-end preview.
   *
   * @param   {String}   id      用于显示HTML的对象ID
   * @param   {Object}   [options={}]  配置选项，可选
   * @returns {Object}   div       返回jQuery对象元素
   */
  editormd.markdownToHTML = function (id, options) {
    var defaults = {
      gfm: true,
      toc: true,
      tocm: false,
      tocStartLevel: 1,
      tocTitle: "目录",
      tocDropdown: false,
      tocContainer: "",
      markdown: "",
      markdownSourceCode: false,
      htmlDecode: false,
      autoLoadKaTeX: true,
      autoLoadMermaid: true,
      pageBreak: true,
      more: true,
      atLink: true, // for @link
      emailLink: true, // for mail address auto link
      tex: false,
      taskList: false, // Github Flavored Markdown task lists
      emoji: false,
      mind: false,
      mermaid: false,
      previewCodeHighlight: true
    };

    editormd.$marked = marked;

    var div = $("#" + id);
    var settings = (div.settings = $.extend(true, defaults, options || {}));
    var saveTo = div.find("textarea");
    if (saveTo.length < 1) {
      div.append("<textarea></textarea>");
      saveTo = div.find("textarea");
    }
    var markdownDoc = settings.markdown === "" ? saveTo.val() : settings.markdown;
    var markdownToC = [];
    var rendererOptions = {
      toc: settings.toc,
      tocm: settings.tocm,
      tocStartLevel: settings.tocStartLevel,
      taskList: settings.taskList,
      emoji: settings.emoji,
      tex: settings.tex,
      pageBreak: settings.pageBreak,
      more: settings.more,
      atLink: settings.atLink, // for @link
      emailLink: settings.emailLink, // for mail address auto link
      previewCodeHighlight: settings.previewCodeHighlight
    };
    var markedOptions = {
      renderer: editormd.markedRenderer(markdownToC, rendererOptions),
      gfm: settings.gfm,
      tables: true,
      breaks: true,
      pedantic: false,
      sanitize: !settings.htmlDecode, // 是否忽略HTML标签，即是否开启HTML标签解析，为了安全性，默认不开启
      smartLists: true,
      smartypants: true,
      langPrefix: "language-" //修改语言类名
    };
    markdownDoc = String(markdownDoc);
    var markdownParsed = marked(markdownDoc, markedOptions);
    markdownParsed = editormd.filterHTMLTags(
      markdownParsed,
      settings.htmlDecode
    );

    //正则匹配 - 分页符
    var nextpage = editormd.regexs.pageBreak;
    if (nextpage.test(markdownParsed)) {
      markdownParsed = markdownParsed.replace(nextpage, "<hr style=\"page-break-after:always;\" class=\"page-break editormd-page-break\" />");
    }

    //正则匹配 - 摘要符
    var more = editormd.regexs.more;
    if (more.test(markdownParsed)) {
      markdownParsed = markdownParsed.replace(more, "<p><img class=\"more\" src=\"\"></p>");
    }

    // FIXED marked.js render table structure with br tag inside, so this make it removed
    function removeBrTagInTable(text) {
      var tableRegex = /<table[^>]*(.|[\n\r])*?<\/table>/gim;
      text = text.replace(tableRegex, function ($1) {
        return $1.replace(/<br>\s*/g, "");
      });
      return text;
    }

    markdownParsed = removeBrTagInTable(markdownParsed);
    if (settings.markdownSourceCode) {
      saveTo.text(markdownDoc);
    } else {
      saveTo.remove();
    }
    div.addClass("markdown-body " + this.classPrefix + "html-preview").append(markdownParsed);
    var tocContainer = settings.tocContainer !== "" ? $(settings.tocContainer) : div;
    if (settings.tocContainer !== "") {
      tocContainer.attr("previewContainer", false);
    }
    if (settings.toc) {
      div.tocContainer = this.markdownToCRenderer(
        markdownToC,
        tocContainer,
        settings.tocDropdown,
        settings.tocStartLevel
      );
      if (
        settings.tocDropdown ||
        div.find("." + this.classPrefix + "toc-menu").length > 0
      ) {
        this.tocDropdownMenu(div, settings.tocTitle);
      }
      if (settings.tocContainer !== "") {
        div.find(".editormd-toc-menu, .editormd-markdown-toc").remove();
      }
    }
    if (settings.previewCodeHighlight) {
      div.find("pre").addClass("prism-highlight");
    }
    if (settings.prismLineNumbers) {
      div.find("pre").addClass("line-numbers");
    }
    if (settings.tex) {
      var katexHandle = function () {
        div.find("." + editormd.classNames.inline_tex).each(function () {
          var tex = $(this);
          katex.render(
            tex.html().replace(/&lt;/g, "<").replace(/&gt;/g, ">"),
            tex[0]
          );
          tex.find(".katex").css("font-size", "1.6em");
        });
      };
      if (settings.autoLoadKaTeX && !editormd.$katex && !editormd.kaTeXLoaded) {
        this.loadKaTeX(function () {
          editormd.$katex = katex;
          editormd.kaTeXLoaded = true;
          katexHandle();
        });
      } else {
        katexHandle();
      }
    }
    if (settings.mind) {
      div.find(".mind").each(function () {
        var mind = $(this);
        mind.drawMind();
        //console.log(mind)
      });
      $(".mind").drawMind();
    }
    if (settings.mermaid) {
      if (settings.autoLoadMermaid && !editormd.$mermaid && !editormd.mermaidLoaded) {
        this.loadMermaid(function () {
          editormd.$mermaid = mermaid;
          editormd.mermaidLoaded = true;
          div.find(".mermaid").each(function () {
            editormd.$mermaid.init({
              startOnLoad: true
            }, ".mermaid");
          });

        });
      }
    }
    div.getMarkdown = function () {
      return saveTo.val();
    };
    return div;
  };

  // Editor.md themes, change toolbar themes etc.
  // added @1.5.0
  editormd.themes = ["default", "dark"];
  // Preview area themes
  // added @1.5.0
  editormd.previewThemes = ["default", "dark"];
  // CodeMirror / editor area themes
  // @1.5.0 rename -> editorThemes, old version -> themes
  editormd.editorThemes = [
    "default",
    "3024-day",
    "3024-night",
    "abcdef",
    "ambiance",
    "ambiance-mobile",
    "base16-dark",
    "base16-light",
    "bespin",
    "blackboard",
    "cobalt",
    "colorforth",
    "dracula",
    "duotone-dark",
    "duotone-light",
    "eclipse",
    "elegant",
    "erlang-dark",
    "gruvbox-dark",
    "hopscotch",
    "icecoder",
    "idea",
    "isotope",
    "lesser-dark",
    "liquibyte",
    "lucario",
    "material",
    "mbo",
    "mdn-like",
    "midnight",
    "monokai",
    "neat",
    "neo",
    "night",
    "oceanic-next",
    "panda-syntax",
    "paraiso-dark",
    "paraiso-light",
    "pastel-on-dark",
    "railscasts",
    "rubyblue",
    "seti",
    "shadowfox",
    "solarized",
    "ssms",
    "the-matrix",
    "tomorrow-night-bright",
    "tomorrow-night-eighties",
    "ttcn",
    "twilight",
    "vibrant-ink",
    "xq-dark",
    "xq-light",
    "yeti",
    "zenburn"
  ];
  editormd.prismThemes = [
    "default",
    "dark",
    "funky",
    "okaidia",
    "twilight",
    "coy",
    "solarizedlight",
    "tomorrow"
  ];
  editormd.loadPlugins = {};
  editormd.loadFiles = {
    js: [],
    css: [],
    plugin: []
  };

  /**
   * 动态加载Editor.md插件，但不立即执行
   * Load editor.md plugins
   *
   * @param {String}   fileName        插件文件路径
   * @param {Function} [callback=function()] 加载成功后执行的回调函数
   * @param {String}   [into="head"]     嵌入页面的位置
   */
  editormd.loadPlugin = function (fileName, callback, into) {
    callback = callback || function () {
    };
    this.loadScript(
      fileName,
      function () {
        editormd.loadFiles.plugin.push(fileName);
        callback();
      },
      into
    );
  };

  /**
   * 动态加载CSS文件的方法
   * Load css file method
   *
   * @param {String}   fileName              CSS文件名
   * @param {Function} [callback=function()] 加载成功后执行的回调函数
   * @param {String}   [into="head"]         嵌入页面的位置
   * @param {Boolean}  [suffix=true]         是否自动加入.css后缀
   */
  editormd.loadCSS = function (fileName, callback = function(){}, into = "head", suffix = true) {
    var css = document.createElement("link");
    css.type = "text/css";
    css.rel = "stylesheet";
    css.onload = css.onreadystatechange = function () {
      editormd.loadFiles.css.push(fileName);
      callback();
    };
    css.href = suffix ? fileName + ".css" : fileName;
    if (into === "head") {
      document.getElementsByTagName("head")[0].appendChild(css);
    } else {
      document.body.appendChild(css);
    }
  };

  editormd.isIE = navigator.appName === "Microsoft Internet Explorer";
  editormd.isIE8 = editormd.isIE && navigator.appVersion.match(/8./i) === "8.";

  /**
   * 动态加载JS文件的方法
   * Load javascript file method
   *
   * @param {String}   fileName        JS文件名
   * @param {Function} [callback=function()] 加载成功后执行的回调函数
   * @param {String}   [into="head"]     嵌入页面的位置
   */
  editormd.loadScript = function (fileName, callback, into) {
    into = into || "head";
    callback = callback || function () {
    };
    var script = null;
    script = document.createElement("script");
    script.id = fileName.replace(/[\./]+/g, "-");
    script.type = "text/javascript";
    script.src = fileName + ".js";
    if (editormd.isIE8) {
      script.onreadystatechange = function () {
        if (script.readyState) {
          if (
            script.readyState === "loaded" ||
            script.readyState === "complete"
          ) {
            script.onreadystatechange = null;
            editormd.loadFiles.js.push(fileName);
            callback();
          }
        }
      };
    } else {
      script.onload = function () {
        editormd.loadFiles.js.push(fileName);
        callback();
      };
    }
    if (into === "head") {
      document.getElementsByTagName("head")[0].appendChild(script);
    } else {
      document.body.appendChild(script);
    }
  };

  // 自定义Prismjs地址
  editormd.prismURL = {
    url: "//cdn.jsdelivr.net/npm/prismjs"
  };

  // 自定义Mirror地址
  editormd.codeMirrorURL = {
    url: "//cdn.jsdelivr.net/npm/codemirror"
  };

  // 自定义Marked地址
  editormd.markedURL = {
    js: "//cdn.jsdelivr.net/npm/marked/lib/marked"
  };

  // 自定义mindmap地址
  editormd.mindMapURL = "lib/mindMap.min";

  // 使用国外的CDN，加载速度有时会很慢，或者自定义URL
  // You can custom KaTeX load url.
  editormd.katexURL = {
    css: "//cdn.jsdelivr.net/npm/katex/dist/katex.min",
    js: "//cdn.jsdelivr.net/npm/katex/dist/katex.min"
  };
  editormd.kaTeXLoaded = false;

  /**
   * 加载KaTeX文件
   * load KaTeX files
   *
   * @param {Function} [callback=function()]  加载成功后执行的回调函数
   */
  editormd.loadKaTeX = function (callback) {
    editormd.loadCSS(editormd.katexURL.css, function () {
      editormd.loadScript(editormd.katexURL.js, callback || function () {
      });
    });
  };

  // 自定义Mermaid地址
  editormd.mermaidURL = {
    js: "//cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min"
  };
  editormd.mermaidLoaded = false;

  /**
   * 加载Mermaid文件
   *
   * @param {Function} [callback=function()]  加载成功后执行的回调函数
   */
  editormd.loadMermaid = function (callback) {
    editormd.loadScript(editormd.mermaidURL.js, callback || function () {

    });
  };

  /**
   * 锁屏
   * lock screen
   *
   * @param   {Boolean}   lock   Boolean 布尔值，是否锁屏
   * @returns {void}
   */
  editormd.lockScreen = function (lock) {
    $("html,body").css("overflow", lock ? "hidden" : "");
  };

  /**
   * 动态创建对话框
   * Creating custom dialogs
   *
   * @param   {Object} options 配置项键值对 Key/Value
   * @returns {dialog} 返回创建的dialog的jQuery实例对象
   */
  editormd.createDialog = function (options) {
    var defaults = {
      name: "",
      width: 420,
      height: 240,
      title: "",
      drag: true,
      closed: true,
      content: "",
      mask: true,
      maskStyle: {
        backgroundColor: "#fff",
        opacity: 0.1
      },
      lockScreen: true,
      footer: true,
      buttons: false
    };

    options = $.extend(true, defaults, options);

    var $this = this;
    var editor = this.editor;
    var classPrefix = editormd.classPrefix;
    var guid = new Date().getTime();
    var dialogName = options.name === "" ? classPrefix + "dialog-" + guid : options.name;
    var mouseOrTouch = editormd.mouseOrTouch;
    var html = "<div class=\"" + classPrefix + "dialog " + dialogName + "\">";
    if (options.title !== "") {
      html +=
        "<div class=\"" +
        classPrefix +
        "dialog-header\"" +
        (options.drag ? " style=\"cursor: move;\"" : "") +
        ">";
      html +=
        "<strong class=\"" +
        classPrefix +
        "dialog-title\">" +
        options.title +
        "</strong>";
      html += "</div>";
    }
    if (options.closed) {
      html +=
        "<a href=\"javascript:;\" class=\"fa fa-close " +
        classPrefix +
        "dialog-close\"></a>";
    }
    html +=
      "<div class=\"" + classPrefix + "dialog-container\">" + options.content;
    if (options.footer || typeof options.footer === "string") {
      html +=
        "<div class=\"" +
        classPrefix +
        "dialog-footer\">" +
        (typeof options.footer === "boolean" ? "" : options.footer) +
        "</div>";
    }
    html += "</div>";
    html +=
      "<div class=\"" +
      classPrefix +
      "dialog-mask " +
      classPrefix +
      "dialog-mask-bg\"></div>";
    html +=
      "<div class=\"" +
      classPrefix +
      "dialog-mask " +
      classPrefix +
      "dialog-mask-con\"></div>";
    html += "</div>";
    editor.append(html);
    var dialog = editor.find("." + dialogName);
    dialog.lockScreen = function (lock) {
      if (options.lockScreen) {
        $("html,body").css("overflow", lock ? "hidden" : "");
        $this.resize();
      }
      return dialog;
    };
    dialog.showMask = function () {
      if (options.mask) {
        editor
          .find("." + classPrefix + "mask")
          .css(options.maskStyle)
          .css("z-index", editormd.dialogZindex - 1)
          .show();
      }
      return dialog;
    };
    dialog.hideMask = function () {
      if (options.mask) {
        editor.find("." + classPrefix + "mask").hide();
      }
      return dialog;
    };
    dialog.loading = function (show) {
      var loading = dialog.find("." + classPrefix + "dialog-mask");
      loading[show ? "show" : "hide"]();
      return dialog;
    };
    dialog.lockScreen(true).showMask();
    dialog.show().css({
      zIndex: editormd.dialogZindex,
      border: editormd.isIE8 ? "1px solid #ddd" : "",
      width:
        typeof options.width === "number"
          ? options.width + "px"
          : options.width,
      height:
        typeof options.height === "number"
          ? options.height + "px"
          : options.height
    });
    var dialogPosition = function () {
      dialog.css({
        top: ($(window).height() - dialog.height()) / 2 + "px",
        left: ($(window).width() - dialog.width()) / 2 + "px"
      });
    };
    dialogPosition();
    $(window).resize(dialogPosition);
    dialog
      .children("." + classPrefix + "dialog-close")
      .bind(mouseOrTouch("click", "touchend"), function () {
        dialog
          .hide()
          .lockScreen(false)
          .hideMask();
      });
    if (typeof options.buttons === "object") {
      var footer = (dialog.footer = dialog.find(
        "." + classPrefix + "dialog-footer"
      ));
      for (var key in options.buttons) {
        var btn = options.buttons[key];
        var btnClassName = classPrefix + key + "-btn";
        footer.append(
          "<button class=\"" +
          classPrefix +
          "btn " +
          btnClassName +
          "\">" +
          btn[0] +
          "</button>"
        );
        btn[1] = $.proxy(btn[1], dialog);
        footer
          .children("." + btnClassName)
          .bind(mouseOrTouch("click", "touchend"), btn[1]);
      }
    }
    if (options.title !== "" && options.drag) {
      var posX, posY;
      var dialogHeader = dialog.children("." + classPrefix + "dialog-header");
      if (!options.mask) {
        dialogHeader.bind(mouseOrTouch("click", "touchend"), function () {
          editormd.dialogZindex += 2;
          dialog.css("z-index", editormd.dialogZindex);
        });
      }
      var userCanSelect = function (obj) {
        obj.removeClass(classPrefix + "user-unselect").off("selectstart");
      };
      var userUnselect = function (obj) {
        obj
          .addClass(classPrefix + "user-unselect")
          .on("selectstart", function () {
            // selectstart for IE
            return false;
          });
      };
      var moveAction = function (e) {
        e = e || window.event; //IE
        var left,
          top,
          nowLeft = parseInt(dialog[0].style.left),
          nowTop = parseInt(dialog[0].style.top);
        if (nowLeft >= 0) {
          if (nowLeft + dialog.width() <= $(window).width()) {
            left = e.clientX - posX;
          } else {
            left = $(window).width() - dialog.width();
            document.onmousemove = null;
          }
        } else {
          left = 0;
          document.onmousemove = null;
        }
        if (nowTop >= 0) {
          top = e.clientY - posY;
        } else {
          top = 0;
          document.onmousemove = null;
        }
        document.onselectstart = function () {
          return false;
        };
        userUnselect($("body"));
        userUnselect(dialog);
        dialog[0].style.left = left + "px";
        dialog[0].style.top = top + "px";
      };
      dialogHeader.mousedown(function (e) {
        e = e || window.event; //IE
        posX = e.clientX - parseInt(dialog[0].style.left);
        posY = e.clientY - parseInt(dialog[0].style.top);
        document.onmousemove = moveAction;
      });
      document.onmouseup = function () {
        userCanSelect($("body"));
        userCanSelect(dialog);
        document.onselectstart = null;
        document.onmousemove = null;
      };
      dialogHeader.touchDraggable = function () {
        var offset = null;
        var start = function (e) {
          var orig = e.originalEvent;
          var pos = $(this)
            .parent()
            .position();
          offset = {
            x: orig.changedTouches[0].pageX - pos.left,
            y: orig.changedTouches[0].pageY - pos.top
          };
        };
        var move = function (e) {
          e.preventDefault();
          var orig = e.originalEvent;
          $(this)
            .parent()
            .css({
              top: orig.changedTouches[0].pageY - offset.y,
              left: orig.changedTouches[0].pageX - offset.x
            });
        };
        this.bind("touchstart", start).bind("touchmove", move);
      };
      dialogHeader.touchDraggable();
    }
    editormd.dialogZindex += 2;
    return dialog;
  };

  /**
   * 鼠标和触摸事件的判断/选择方法
   * MouseEvent or TouchEvent type switch
   *
   * @param   {String} [mouseEventType="click"]  供选择的鼠标事件
   * @param   {String} [touchEventType="touchend"] 供选择的触摸事件
   * @returns {String} EventType           返回事件类型名称
   */
  editormd.mouseOrTouch = function (mouseEventType, touchEventType) {
    mouseEventType = mouseEventType || "click";
    touchEventType = touchEventType || "touchend";
    var eventType = mouseEventType;
    var userAgentInfo = navigator.userAgent;
    var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
    var flag = true;
    for (var v = 0; v < Agents.length; v++) {
      if (userAgentInfo.indexOf(Agents[v]) > 0) {
        flag = false;
        break;
      }
    }
    if (!flag) {
      eventType = touchEventType;
    }

    return eventType;
  };

  /**
   * 日期时间的格式化方法
   * Datetime format method
   *
   * @param   {String}   [format=""]  日期时间的格式，类似PHP的格式
   * @returns {String}   datefmt    返回格式化后的日期时间字符串
   */
  editormd.dateFormat = function (format) {
    format = format || "";
    var addZero = function (d) {
      return d < 10 ? "0" + d : d;
    };
    var date = new Date();
    var year = date.getFullYear();
    var year2 = year.toString().slice(2, 4);
    var month = addZero(date.getMonth() + 1);
    var day = addZero(date.getDate());
    var weekDay = date.getDay();
    var hour = addZero(date.getHours());
    var min = addZero(date.getMinutes());
    var second = addZero(date.getSeconds());
    var ms = addZero(date.getMilliseconds());
    var datefmt = "";
    var ymd = year2 + "-" + month + "-" + day;
    var fymd = year + "-" + month + "-" + day;
    var hms = hour + ":" + min + ":" + second;
    switch (format) {
      case "UNIX Time":
        datefmt = date.getTime();
        break;
      case "UTC":
        datefmt = date.toUTCString();
        break;
      case "yy":
        datefmt = year2;
        break;
      case "year":
      case "yyyy":
        datefmt = year;
        break;
      case "month":
      case "mm":
        datefmt = month;
        break;
      case "cn-week-day":
      case "cn-wd":
        var cnWeekDays = ["日", "一", "二", "三", "四", "五", "六"];
        datefmt = "星期" + cnWeekDays[weekDay];
        break;
      case "week-day":
      case "wd":
        var weekDays = [
          "Sunday",
          "Monday",
          "Tuesday",
          "Wednesday",
          "Thursday",
          "Friday",
          "Saturday"
        ];
        datefmt = weekDays[weekDay];
        break;
      case "day":
      case "dd":
        datefmt = day;
        break;
      case "hour":
      case "hh":
        datefmt = hour;
        break;
      case "min":
      case "ii":
        datefmt = min;
        break;
      case "second":
      case "ss":
        datefmt = second;
        break;
      case "ms":
        datefmt = ms;
        break;
      case "yy-mm-dd":
        datefmt = ymd;
        break;
      case "yyyy-mm-dd":
        datefmt = fymd;
        break;
      case "yyyy-mm-dd h:i:s ms":
      case "full + ms":
        datefmt = fymd + " " + hms + " " + ms;
        break;
      case "full":
      case "yyyy-mm-dd h:i:s":
        break;
      default:
        datefmt = fymd + " " + hms;
        break;
    }
    return datefmt;
  };

  return editormd;
});
